Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Date input #368

Merged
merged 2 commits into from
Apr 15, 2024
Merged

New Date input #368

merged 2 commits into from
Apr 15, 2024

Conversation

chiragchhatrala
Copy link
Collaborator

@chiragchhatrala chiragchhatrala commented Apr 4, 2024

Summary by CodeRabbit

  • New Features
    • Enhanced date input functionality with a new popover feature for easier selection.
    • Added dark mode support for form fields, improving visibility and user experience in low-light conditions.
    • Introduced new form input options such as timezone and date format customization, allowing for more personalized form submissions.
    • Implemented a new theme for DateInput with improved styling for labels, inputs, and help text.
  • Refactor
    • Refined form submission formatting for better readability and date handling.
    • Refactored component structures and templates for improved performance and maintainability.
    • Utilized the Composition API and script setup for more efficient code organization in Vue components.
  • Style
    • Made styling adjustments to accommodate new functionalities and themes, including changes for dark mode compatibility.
  • Bug Fixes
    • Fixed date handling logic to correctly prefill dates and format them based on specified settings.

@chiragchhatrala chiragchhatrala requested a review from JhumanJ April 4, 2024 19:16
Copy link
Contributor

coderabbitai bot commented Apr 4, 2024

Walkthrough

The recent updates encompass enhancements in form submission formatting, date input functionality, and user interface improvements across various components. Key changes include better readability in code, refined date handling in form fields, introduction of new computed properties and methods, and the adoption of the composition API for more efficient component structuring. Additionally, there's a focus on user experience with adjustments in styling and the integration of new features like a popover for date inputs and a toggle switch for options.

Changes

File(s) Summary
app/Service/Forms/FormSubmissionFormatter.php Improved string concatenation readability and adjusted date formatting logic.
client/components/forms/DateInput.vue Overhauled component with new structure, enhanced date functionality, computed properties, and styling changes.
client/components/open/forms/OpenForm.vue & client/components/open/forms/OpenFormField.vue Updated submission ID retrieval, simplified date handling, and integrated dark mode settings.
client/components/open/forms/fields/components/FieldOptions.vue & client/components/open/tables/components/OpenDate.vue Refactored to use new input components, added date format handling, and introduced formatted date method.
client/lib/colors.js & client/lib/forms/form-themes.js Added functions for color conversions and generating palettes; introduced new theme properties for date input and input spacing.
client/pages/forms/[slug]/index.vue Implemented loader logic based on submission ID query parameters.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Review Status

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between a751725 and 1cd148f.
Files ignored due to path filters (1)
  • client/package.json is excluded by !**/*.json
Files selected for processing (9)
  • app/Service/Forms/FormSubmissionFormatter.php (6 hunks)
  • client/components/forms/DateInput.vue (1 hunks)
  • client/components/open/forms/OpenForm.vue (3 hunks)
  • client/components/open/forms/OpenFormField.vue (4 hunks)
  • client/components/open/forms/fields/components/FieldOptions.vue (7 hunks)
  • client/components/open/tables/components/OpenDate.vue (2 hunks)
  • client/lib/colors.js (1 hunks)
  • client/lib/forms/form-themes.js (3 hunks)
  • client/pages/forms/[slug]/index.vue (1 hunks)
Additional comments not posted (12)
client/components/forms/DateInput.vue (1)

99-108: Use defineProps to declare props with their types and default values for better type safety and readability.

app/Service/Forms/FormSubmissionFormatter.php (1)

104-104: Ensure consistency in the use of spaces around the concatenation operator for better readability.

client/lib/forms/form-themes.js (2)

18-22: Ensure the new DateInput theme aligns with the application's design system and accessibility standards.


9-12: The addition of inputSpacing properties enhances the flexibility of theme customization. Consider documenting these changes in the theme customization guide.

client/components/open/forms/OpenFormField.vue (3)

59-59: The import of darkModeEnabled is correctly implemented.


180-182: The implementation of the isDark computed property is correct and efficiently checks for dark mode settings.


252-253: The modifications in the inputProperties method, including the addition of isDark and enhancements in date handling, are correctly implemented and align with the PR objectives.

Also applies to: 269-273

client/components/open/forms/OpenForm.vue (2)

341-342: The retrieval of submission_id from the query parameter is correctly implemented and enhances the form's functionality for pre-filling or editing based on specific submissions.


355-355: The simplification of date handling for prefilling with 'today' using new Date().toISOString() is correctly implemented and improves the efficiency and consistency of date inputs.

Also applies to: 389-389

client/components/open/forms/fields/components/FieldOptions.vue (3)

116-162: Ensure proper validation and error handling for the new date-related inputs (date_range, prefill_today, disable_past_dates, disable_future_dates, with_time, timezone, date_format) to prevent invalid configurations.


328-328: Ensure that the format function from date-fns is used securely and efficiently, especially when formatting user-provided dates.

Verification successful

To accurately assess the security and efficiency of using the format function with potentially user-provided dates, we need to understand the origin and handling of the date and dateFormat variables. Specifically, it's crucial to know if these variables are user-provided and, if so, how they are validated or sanitized before being passed to the format function.


Given the limitations of the search results and the need to understand the origin and handling of the date and dateFormat variables to assess the security and efficiency of their use with the format function, a broader search strategy is required. We need to search for any references to these variables throughout the entire component file, which might provide clues about their origin and handling.


Based on the provided context, the usage of the format function from date-fns within the FieldOptions.vue component is secure and efficient. The date variable is a newly created Date object, and the dateFormat values are predefined within the component, minimizing the risk associated with formatting user-provided dates. The original concern about securely and efficiently formatting user-provided dates does not apply in this scenario, as both date and dateFormat are controlled and not directly influenced by user input. Therefore, the usage is consistent with best practices for handling dates in Vue components.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Check for usage of the `format` function with user-provided dates.
rg "format\(" client/components/open/forms/fields/components/FieldOptions.vue

Length of output: 121



Script:

#!/bin/bash
# Check the origin and handling of `date` and `dateFormat` variables before they are passed to the `format` function.
rg "date," client/components/open/forms/fields/components/FieldOptions.vue
rg "dateFormat," client/components/open/forms/fields/components/FieldOptions.vue

Length of output: 197



Script:

#!/bin/bash
# Broaden the search for any references to `date` and `dateFormat` variables in the entire component file.
rg "date" client/components/open/forms/fields/components/FieldOptions.vue
rg "dateFormat" client/components/open/forms/fields/components/FieldOptions.vue

Length of output: 2811


383-391: Validate the dateFormatOptions computed property to ensure it handles edge cases, such as invalid date formats or locales that might affect the format function.

Comment on lines +12 to +13
import { format } from 'date-fns'
import { default as _has } from 'lodash/has'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider importing only the necessary functions from lodash to reduce bundle size.

- import { default as _has } from 'lodash/has'
+ import _has from 'lodash/has'

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
import { format } from 'date-fns'
import { default as _has } from 'lodash/has'
import { format } from 'date-fns'
import _has from 'lodash/has'

Comment on lines +39 to +54
formattedDate(val) {
if (!val) return ''
const dateFormat = _has(this.property, 'date_format') ? this.property.date_format : 'dd/MM/yyyy'
if (this.property?.with_time) {
try {
return format(new Date(val), dateFormat + ' HH:mm')
} catch (e) {
return ''
}
}
try {
return format(new Date(val), dateFormat)
} catch (e) {
return ''
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper error handling and logging for the formattedDate method.

  try {
    return format(new Date(val), dateFormat)
  } catch (e) {
+   console.error(`Error formatting date: ${e.message}`);
    return ''
  }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
formattedDate(val) {
if (!val) return ''
const dateFormat = _has(this.property, 'date_format') ? this.property.date_format : 'dd/MM/yyyy'
if (this.property?.with_time) {
try {
return format(new Date(val), dateFormat + ' HH:mm')
} catch (e) {
return ''
}
}
try {
return format(new Date(val), dateFormat)
} catch (e) {
return ''
}
}
formattedDate(val) {
if (!val) return ''
const dateFormat = _has(this.property, 'date_format') ? this.property.date_format : 'dd/MM/yyyy'
if (this.property?.with_time) {
try {
return format(new Date(val), dateFormat + ' HH:mm')
} catch (e) {
return ''
}
}
try {
return format(new Date(val), dateFormat)
} catch (e) {
console.error(`Error formatting date: ${e.message}`);
return ''
}
}

Comment on lines +1 to +113
s,
l = (max + min) / 2

if (max === min) {
h = s = 0 // achromatic
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}

return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }
} catch (error) {
console.error('Invalid HEX color', hex)
return { h: 0, s: 0, l: 0 }
}
}

// hslToHex.js functionality
const hslToHex = ({ h, s, l }) => {
l /= 100
const a = (s * Math.min(l, 1 - l)) / 100
const f = n => {
const k = (n + h / 30) % 12
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
return Math.round(255 * color).toString(16).padStart(2, '0')
}
return `#${f(0)}${f(8)}${f(4)}`
}

// generateColor.js functionality
const generateColor = ({ hex, preserve, shades }) => {
const colorHSL = hexToHSL(hex)
const obj = {}
const lightnessDelta = {}

shades.forEach(({ name, lightness }) => {
const { h, s, l } = colorHSL
const hsl = { h, s, l: lightness }
const hex = hslToHex(hsl)
obj[name] = hex
if (preserve) lightnessDelta[name] = Math.abs(l - lightness)
})

if (preserve) {
const [closestShade] = Object.keys(lightnessDelta).sort(
(a, b) => lightnessDelta[a] - lightnessDelta[b]
)
obj[closestShade] = hex
}

return obj
}

// tailwindcssPaletteGenerator functionality
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]

if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
options = Object.assign({ colors, names, preserve, shades }, options)
}

const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})

return palette
}

// Exporting the functions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper error handling in the hexToHSL and hslToHex functions to avoid silent failures.

  } catch (error) {
+   throw new Error(`Failed to convert color: ${error.message}`);
    console.error('Invalid HEX color', hex)
    return { h: 0, s: 0, l: 0 }
  }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// hexToHSL.js functionality
const hexToHSL = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) || []
try {
let r = parseInt(result[1], 16)
let g = parseInt(result[2], 16)
let b = parseInt(result[3], 16)
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b),
min = Math.min(r, g, b)
let h = 0,
s,
l = (max + min) / 2
if (max === min) {
h = s = 0 // achromatic
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }
} catch (error) {
console.error('Invalid HEX color', hex)
return { h: 0, s: 0, l: 0 }
}
}
// hslToHex.js functionality
const hslToHex = ({ h, s, l }) => {
l /= 100
const a = (s * Math.min(l, 1 - l)) / 100
const f = n => {
const k = (n + h / 30) % 12
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
return Math.round(255 * color).toString(16).padStart(2, '0')
}
return `#${f(0)}${f(8)}${f(4)}`
}
// generateColor.js functionality
const generateColor = ({ hex, preserve, shades }) => {
const colorHSL = hexToHSL(hex)
const obj = {}
const lightnessDelta = {}
shades.forEach(({ name, lightness }) => {
const { h, s, l } = colorHSL
const hsl = { h, s, l: lightness }
const hex = hslToHex(hsl)
obj[name] = hex
if (preserve) lightnessDelta[name] = Math.abs(l - lightness)
})
if (preserve) {
const [closestShade] = Object.keys(lightnessDelta).sort(
(a, b) => lightnessDelta[a] - lightnessDelta[b]
)
obj[closestShade] = hex
}
return obj
}
// tailwindcssPaletteGenerator functionality
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]
if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
options = Object.assign({ colors, names, preserve, shades }, options)
}
const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})
return palette
}
// Exporting the functions
// hexToHSL.js functionality
const hexToHSL = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) || []
try {
let r = parseInt(result[1], 16)
let g = parseInt(result[2], 16)
let b = parseInt(result[3], 16)
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b),
min = Math.min(r, g, b)
let h = 0,
s,
l = (max + min) / 2
if (max === min) {
h = s = 0 // achromatic
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }
} catch (error) {
throw new Error(`Failed to convert color: ${error.message}`);
console.error('Invalid HEX color', hex)
return { h: 0, s: 0, l: 0 }
}
}
// hslToHex.js functionality
const hslToHex = ({ h, s, l }) => {
l /= 100
const a = (s * Math.min(l, 1 - l)) / 100
const f = n => {
const k = (n + h / 30) % 12
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
return Math.round(255 * color).toString(16).padStart(2, '0')
}
return `#${f(0)}${f(8)}${f(4)}`
}
// generateColor.js functionality
const generateColor = ({ hex, preserve, shades }) => {
const colorHSL = hexToHSL(hex)
const obj = {}
const lightnessDelta = {}
shades.forEach(({ name, lightness }) => {
const { h, s, l } = colorHSL
const hsl = { h, s, l: lightness }
const hex = hslToHex(hsl)
obj[name] = hex
if (preserve) lightnessDelta[name] = Math.abs(l - lightness)
})
if (preserve) {
const [closestShade] = Object.keys(lightnessDelta).sort(
(a, b) => lightnessDelta[a] - lightnessDelta[b]
)
obj[closestShade] = hex
}
return obj
}
// tailwindcssPaletteGenerator functionality
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]
if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
options = Object.assign({ colors, names, preserve, shades }, options)
}
const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})
return palette
}
// Exporting the functions

Comment on lines +11 to +16
<toggle-switch-input :form="field" name="required" label="Required"
@update:model-value="onFieldRequiredChange"/>
<toggle-switch-input :form="field" name="hidden" label="Hidden"
@update:model-value="onFieldHiddenChange"/>
<toggle-switch-input :form="field" name="disabled" label="Disabled"
@update:model-value="onFieldDisabledChange"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a brief description as a tooltip or help prop for each toggle-switch-input to enhance user understanding.

  <toggle-switch-input :form="field" name="required" label="Required"
+ help="Mark the field as mandatory for form submission."
    @update:model-value="onFieldRequiredChange"/>
  <toggle-switch-input :form="field" name="hidden" label="Hidden"
+ help="Hide this field from the form view."
    @update:model-value="onFieldHiddenChange"/>
  <toggle-switch-input :form="field" name="disabled" label="Disabled"
+ help="Disable input for this field."
    @update:model-value="onFieldDisabledChange"/>

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
<toggle-switch-input :form="field" name="required" label="Required"
@update:model-value="onFieldRequiredChange"/>
<toggle-switch-input :form="field" name="hidden" label="Hidden"
@update:model-value="onFieldHiddenChange"/>
<toggle-switch-input :form="field" name="disabled" label="Disabled"
@update:model-value="onFieldDisabledChange"/>
<toggle-switch-input :form="field" name="required" label="Required"
help="Mark the field as mandatory for form submission."
@update:model-value="onFieldRequiredChange"/>
<toggle-switch-input :form="field" name="hidden" label="Hidden"
help="Hide this field from the form view."
@update:model-value="onFieldHiddenChange"/>
<toggle-switch-input :form="field" name="disabled" label="Disabled"
help="Disable input for this field."
@update:model-value="onFieldDisabledChange"/>

Comment on lines +569 to +571
date: {
date_format: this.dateFormatOptions[0].value
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider setting a default date_format based on the user's locale or the most common format used within the application to enhance usability.

  date: {
-   date_format: this.dateFormatOptions[0].value
+   date_format: this.getDefaultDateFormat()
  }

Add a method getDefaultDateFormat that determines the default date format based on the user's locale or application settings.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
date: {
date_format: this.dateFormatOptions[0].value
}
date: {
date_format: this.getDefaultDateFormat()
}

Comment on lines +80 to +111
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]

if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
options = Object.assign({ colors, names, preserve, shades }, options)
}

const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})

return palette
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation for the options parameter in tailwindcssPaletteGenerator to ensure it meets expected structure and types.

  if (typeof options === 'object' && !Array.isArray(options)) {
+   if (!options.colors || !Array.isArray(options.colors)) {
+     throw new Error('Invalid options: colors must be an array');
+   }
    options = Object.assign({ colors, names, preserve, shades }, options)
  }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]
if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
options = Object.assign({ colors, names, preserve, shades }, options)
}
const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})
return palette
}
const tailwindcssPaletteGenerator = (options) => {
let colors = []
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
let preserve = true
let shades = [
{ name: '50', lightness: 98 },
{ name: '100', lightness: 95 },
{ name: '200', lightness: 90 },
{ name: '300', lightness: 82 },
{ name: '400', lightness: 64 },
{ name: '500', lightness: 46 },
{ name: '600', lightness: 33 },
{ name: '700', lightness: 24 },
{ name: '800', lightness: 14 },
{ name: '900', lightness: 7 },
{ name: '950', lightness: 4 }
]
if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
if (typeof options === 'object' && !Array.isArray(options)) {
if (!options.colors || !Array.isArray(options.colors)) {
throw new Error('Invalid options: colors must be an array');
}
options = Object.assign({ colors, names, preserve, shades }, options)
}
const palette = {}
options.colors.forEach((hex, i) => {
const name = options.names[i]
palette[name] = generateColor({ hex, preserve, shades: options.shades })
})
return palette
}

Comment on lines +116 to +119
// Start loader if record needs to be loaded
if (useRoute().query?.submission_id) {
recordsStore.startLoading()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the submission_id from the query parameters is sanitized before use to prevent potential security issues.

if (useRoute().query?.submission_id) {
+ // Ensure submission_id is sanitized
  recordsStore.startLoading()
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// Start loader if record needs to be loaded
if (useRoute().query?.submission_id) {
recordsStore.startLoading()
}
// Start loader if record needs to be loaded
if (useRoute().query?.submission_id) {
// Ensure submission_id is sanitized
recordsStore.startLoading()
}

Comment on lines +90 to +240
* @param event
* @returns {boolean}
*/
onEnterPress (event) {
event.preventDefault()
return false
},
setInputColor () {
if (this.$refs.datepicker) {
const dateInput = this.$refs.datepicker.$el.getElementsByTagName('input')[0]
dateInput.style.setProperty('--tw-ring-color', this.color)
}
},
dateToUTC (val) {
if (!val) {
return null
}
if (!this.useTime) {
return val
}
return new Date(val).toISOString()
},
dateToLocal (val) {
if (!val) {
return null
}
const dateObj = new Date(val)
let dateStr = dateObj.getFullYear() + '-' +
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
String(dateObj.getDate()).padStart(2, '0')
if (this.useTime) {
dateStr += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
String(dateObj.getMinutes()).padStart(2, '0')
}
return dateStr
const clear = () => {
fromDate.value = null
toDate.value = null
pickerOpen.value = false
}

const formattedDate = (value) => {
if (props.withTime) {
try {
return format(new Date(value), props.dateFormat + ' HH:mm')
} catch (e) {
return ''
}
}
try {
return format(new Date(value), props.dateFormat)
} catch (e) {
return ''
}
}

const formattedDatePreview = computed(() => {
if (!fromDate.value) return ''
if (props.dateRange) {
if (!toDate.value) return formattedDate(fromDate.value)
return `${formattedDate(fromDate.value)} - ${formattedDate(toDate.value)}`
}
return formattedDate(fromDate.value)
})

watch(() => props.color, () => {
setInputColor()
}, { immediate: true })

watch(() => props.dateRange, () => {
fromDate.value = null
toDate.value = null
}, { immediate: true })

watch(() => fromDate.value, (val) => {
if (props.dateRange) {
if (!Array.isArray(input.compVal.value)) input.compVal.value = []
input.compVal.value[0] = val
} else {
input.compVal.value = val
}
}, { immediate: false })

watch(() => toDate.value, (val) => {
if (props.dateRange) {
if (!Array.isArray(input.compVal.value)) input.compVal.value = [null]
input.compVal.value[1] = val
} else {
input.compVal.value = null
}
}, { immediate: false })

watch(() => input.compVal.value, (val, oldVal) => {
if (!oldVal) handleCompValChange()
}, { immediate: false })

onMounted(() => {
handleCompValChange()
setInputColor()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor the formattedDate method to avoid code duplication.

const formattedDate = (value) => {
  let formatString = props.dateFormat;
+ if (props.withTime) {
+   formatString += ' HH:mm';
+ }
  try {
-   if (props.withTime) {
-     return format(new Date(value), props.dateFormat + ' HH:mm')
-   }
-   return format(new Date(value), props.dateFormat)
+   return format(new Date(value), formatString);
  } catch (e) {
    return ''
  }
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
<script setup>
import { inputProps, useFormInput } from './useFormInput.js'
import InputWrapper from './components/InputWrapper.vue'
import { getCurrentInstance } from 'vue'
import { DatePicker } from 'v-calendar'
import 'v-calendar/dist/style.css'
import { format } from 'date-fns'
import { tailwindcssPaletteGenerator } from '~/lib/colors.js'
export default {
name: 'DateInput',
components: { InputWrapper },
mixins: [],
props: {
...inputProps,
withTime: { type: Boolean, default: false },
dateRange: { type: Boolean, default: false },
disablePastDates: { type: Boolean, default: false },
disableFutureDates: { type: Boolean, default: false }
},
const props = defineProps({
...inputProps,
withTime: { type: Boolean, default: false },
dateRange: { type: Boolean, default: false },
disablePastDates: { type: Boolean, default: false },
disableFutureDates: { type: Boolean, default: false },
dateFormat: { type: String, default: 'dd/MM/yyyy' },
outputDateFormat: { type: String, default: 'yyyy-MM-dd\'T\'HH:mm:ssXXX' },
isDark: { type: Boolean, default: false }
})
setup (props, context) {
return {
...useFormInput(props, context)
}
},
const input = useFormInput(props, getCurrentInstance())
const fromDate = ref(null)
const toDate = ref(null)
const datepicker = ref(null)
const pickerOpen = ref(false)
data: () => ({
fromDate: null,
toDate: null
}),
computed: {
inputClasses () {
let str = 'border border-gray-300 dark:bg-notion-dark-light dark:border-gray-600 dark:placeholder-gray-500 dark:text-gray-300 flex-1 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-opacity-100 placeholder-gray-400 px-4 py-2 rounded-lg shadow-sm text-base text-black text-gray-700'
str += this.dateRange ? ' w-50' : ' w-full'
str += this.disabled ? ' !cursor-not-allowed !bg-gray-200' : ''
return str
},
useTime () {
return this.withTime && !this.dateRange
},
setMinDate () {
if (this.disablePastDates) {
return new Date().toISOString().split('T')[0]
}
return false
},
setMaxDate () {
if (this.disableFutureDates) {
return new Date().toISOString().split('T')[0]
}
return false
}
},
const twColors = computed(() => {
return tailwindcssPaletteGenerator(props.color).primary
})
watch: {
color: {
handler () {
this.setInputColor()
},
immediate: true
},
fromDate: {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = []
}
this.compVal[0] = this.dateToUTC(val)
} else {
this.compVal = this.dateToUTC(val)
}
},
immediate: false
},
toDate: {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = [null]
}
this.compVal[1] = this.dateToUTC(val)
} else {
this.compVal = null
}
},
immediate: false
}
const modeledValue = computed({
get () {
return props.dateRange ? { start: fromDate.value, end: toDate.value } : fromDate.value
},
set (value) {
if (props.dateRange) {
fromDate.value = format(value.start, props.outputDateFormat)
toDate.value = format(value.end, props.outputDateFormat)
} else {
fromDate.value = format(value, props.outputDateFormat)
}
}
})
mounted () {
if (this.compVal) {
if (Array.isArray(this.compVal)) {
this.fromDate = this.compVal[0] ?? null
this.toDate = this.compVal[1] ?? null
} else {
this.fromDate = this.dateToLocal(this.compVal)
}
const inputClasses = computed(() => {
const classes = [props.theme.DateInput.input, 'w-full']
if (props.disabled) {
classes.push('!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200')
}
if (input.hasError.value) {
classes.push('!ring-red-500 !ring-2 !border-transparent')
}
return classes.join(' ')
})
const minDate = computed(() => {
if (props.disablePastDates) {
return new Date()
}
return undefined
})
const maxDate = computed(() => {
if (props.disableFutureDates) {
return new Date()
}
return undefined
})
const handleCompValChange = () => {
if (input.compVal.value) {
if (Array.isArray(input.compVal.value)) {
fromDate.value = input.compVal.value[0] ?? null
toDate.value = input.compVal.value[1] ?? null
} else {
fromDate.value = input.compVal.value
}
}
}
this.setInputColor()
},
const setInputColor = () => {
if (datepicker.value) {
const dateInput = datepicker.value.$el.getElementsByTagName('input')[0]
dateInput.style.setProperty('--tw-ring-color', props.color)
}
}
methods: {
/**
* Pressing enter won't submit form
* @param event
* @returns {boolean}
*/
onEnterPress (event) {
event.preventDefault()
return false
},
setInputColor () {
if (this.$refs.datepicker) {
const dateInput = this.$refs.datepicker.$el.getElementsByTagName('input')[0]
dateInput.style.setProperty('--tw-ring-color', this.color)
}
},
dateToUTC (val) {
if (!val) {
return null
}
if (!this.useTime) {
return val
}
return new Date(val).toISOString()
},
dateToLocal (val) {
if (!val) {
return null
}
const dateObj = new Date(val)
let dateStr = dateObj.getFullYear() + '-' +
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
String(dateObj.getDate()).padStart(2, '0')
if (this.useTime) {
dateStr += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
String(dateObj.getMinutes()).padStart(2, '0')
}
return dateStr
const clear = () => {
fromDate.value = null
toDate.value = null
pickerOpen.value = false
}
const formattedDate = (value) => {
if (props.withTime) {
try {
return format(new Date(value), props.dateFormat + ' HH:mm')
} catch (e) {
return ''
}
}
try {
return format(new Date(value), props.dateFormat)
} catch (e) {
return ''
}
}
const formattedDatePreview = computed(() => {
if (!fromDate.value) return ''
if (props.dateRange) {
if (!toDate.value) return formattedDate(fromDate.value)
return `${formattedDate(fromDate.value)} - ${formattedDate(toDate.value)}`
}
return formattedDate(fromDate.value)
})
watch(() => props.color, () => {
setInputColor()
}, { immediate: true })
watch(() => props.dateRange, () => {
fromDate.value = null
toDate.value = null
}, { immediate: true })
watch(() => fromDate.value, (val) => {
if (props.dateRange) {
if (!Array.isArray(input.compVal.value)) input.compVal.value = []
input.compVal.value[0] = val
} else {
input.compVal.value = val
}
}, { immediate: false })
watch(() => toDate.value, (val) => {
if (props.dateRange) {
if (!Array.isArray(input.compVal.value)) input.compVal.value = [null]
input.compVal.value[1] = val
} else {
input.compVal.value = null
}
}, { immediate: false })
watch(() => input.compVal.value, (val, oldVal) => {
if (!oldVal) handleCompValChange()
}, { immediate: false })
onMounted(() => {
handleCompValChange()
setInputColor()
})
const formattedDate = (value) => {
let formatString = props.dateFormat;
if (props.withTime) {
formatString += ' HH:mm';
}
try {
return format(new Date(value), formatString);
} catch (e) {
return ''
}
}

Comment on lines +205 to +211
$dateFormat = ($field['date_format'] ?? 'dd/MM/yyyy') == 'dd/MM/yyyy' ? 'd/m/Y' : 'm/d/Y';
$dateFormat .= (isset($field['with_time']) && $field['with_time']) ? ' H:i' : '';
if (is_array($data[$field['id']])) {
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format('d/m/Y')
.' - '.(new Carbon($data[$field['id']][1]))->format('d/m/Y') : (new Carbon($data[$field['id']][0]))->format('d/m/Y');
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format($dateFormat)
. ' - ' . (new Carbon($data[$field['id']][1]))->format($dateFormat) : (new Carbon($data[$field['id']][0]))->format($dateFormat);
} else {
$field['value'] = (new Carbon($data[$field['id']]))->format((isset($field['with_time']) && $field['with_time']) ? 'd/m/Y H:i' : 'd/m/Y');
$field['value'] = (new Carbon($data[$field['id']]))->format($dateFormat);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate the date format before applying it to ensure it matches expected patterns and avoid runtime errors.

$dateFormat = ($field['date_format'] ?? 'dd/MM/yyyy') == 'dd/MM/yyyy' ? 'd/m/Y' : 'm/d/Y';
+ // Ensure dateFormat is valid
$dateFormat .= (isset($field['with_time']) && $field['with_time']) ? ' H:i' : '';

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
$dateFormat = ($field['date_format'] ?? 'dd/MM/yyyy') == 'dd/MM/yyyy' ? 'd/m/Y' : 'm/d/Y';
$dateFormat .= (isset($field['with_time']) && $field['with_time']) ? ' H:i' : '';
if (is_array($data[$field['id']])) {
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format('d/m/Y')
.' - '.(new Carbon($data[$field['id']][1]))->format('d/m/Y') : (new Carbon($data[$field['id']][0]))->format('d/m/Y');
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format($dateFormat)
. ' - ' . (new Carbon($data[$field['id']][1]))->format($dateFormat) : (new Carbon($data[$field['id']][0]))->format($dateFormat);
} else {
$field['value'] = (new Carbon($data[$field['id']]))->format((isset($field['with_time']) && $field['with_time']) ? 'd/m/Y H:i' : 'd/m/Y');
$field['value'] = (new Carbon($data[$field['id']]))->format($dateFormat);
$dateFormat = ($field['date_format'] ?? 'dd/MM/yyyy') == 'dd/MM/yyyy' ? 'd/m/Y' : 'm/d/Y';
// Ensure dateFormat is valid
$dateFormat .= (isset($field['with_time']) && $field['with_time']) ? ' H:i' : '';
if (is_array($data[$field['id']])) {
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format($dateFormat)
. ' - ' . (new Carbon($data[$field['id']][1]))->format($dateFormat) : (new Carbon($data[$field['id']][0]))->format($dateFormat);
} else {
$field['value'] = (new Carbon($data[$field['id']]))->format($dateFormat);

@JhumanJ JhumanJ merged commit 4f4f712 into main Apr 15, 2024
5 checks passed
@JhumanJ JhumanJ deleted the 47007-new-date-input branch April 15, 2024 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants