-
Notifications
You must be signed in to change notification settings - Fork 344
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
New Date input #368
Conversation
WalkthroughThe 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
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? TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Additionally, you can add CodeRabbit Configration File (
|
There was a problem hiding this 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
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
: UsedefineProps
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 newDateInput
theme aligns with the application's design system and accessibility standards.
9-12
: The addition ofinputSpacing
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 ofdarkModeEnabled
is correctly implemented.
180-182
: The implementation of theisDark
computed property is correct and efficiently checks for dark mode settings.
252-253
: The modifications in theinputProperties
method, including the addition ofisDark
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 ofsubmission_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' usingnew 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 theformat
function fromdate-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 thedate
anddateFormat
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 theformat
function.
Given the limitations of the search results and the need to understand the origin and handling of the
date
anddateFormat
variables to assess the security and efficiency of their use with theformat
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 fromdate-fns
within theFieldOptions.vue
component is secure and efficient. Thedate
variable is a newly createdDate
object, and thedateFormat
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 bothdate
anddateFormat
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.vueLength 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.vueLength 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.vueLength of output: 2811
383-391
: Validate thedateFormatOptions
computed property to ensure it handles edge cases, such as invalid date formats or locales that might affect theformat
function.
import { format } from 'date-fns' | ||
import { default as _has } from 'lodash/has' |
There was a problem hiding this comment.
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.
import { format } from 'date-fns' | |
import { default as _has } from 'lodash/has' | |
import { format } from 'date-fns' | |
import _has from 'lodash/has' |
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 '' | ||
} | ||
} |
There was a problem hiding this comment.
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.
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 '' | |
} | |
} |
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 |
There was a problem hiding this comment.
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.
// 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 |
<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"/> |
There was a problem hiding this comment.
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.
<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"/> |
date: { | ||
date_format: this.dateFormatOptions[0].value | ||
} |
There was a problem hiding this comment.
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.
date: { | |
date_format: this.dateFormatOptions[0].value | |
} | |
date: { | |
date_format: this.getDefaultDateFormat() | |
} |
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 | ||
} |
There was a problem hiding this comment.
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.
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 | |
} |
// Start loader if record needs to be loaded | ||
if (useRoute().query?.submission_id) { | ||
recordsStore.startLoading() | ||
} |
There was a problem hiding this comment.
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.
// 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() | |
} |
* @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() | ||
}) |
There was a problem hiding this comment.
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.
<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 '' | |
} | |
} |
$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); |
There was a problem hiding this comment.
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.
$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); |
Summary by CodeRabbit
DateInput
with improved styling for labels, inputs, and help text.