diff --git a/app/Service/Forms/FormSubmissionFormatter.php b/app/Service/Forms/FormSubmissionFormatter.php index e88c24a9..a885a8db 100644 --- a/app/Service/Forms/FormSubmissionFormatter.php +++ b/app/Service/Forms/FormSubmissionFormatter.php @@ -101,7 +101,7 @@ class FormSubmissionFormatter $fields = $fields->merge($removeFields); } $fields = $fields->filter(function ($field) { - return ! in_array($field['type'], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']); + return !in_array($field['type'], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']); })->values(); $returnArray = []; @@ -112,14 +112,14 @@ class FormSubmissionFormatter } if ($field['removed'] ?? false) { - $field['name'] = $field['name'].' (deleted)'; + $field['name'] = $field['name'] . ' (deleted)'; } // Add ID to avoid name clashes - $field['name'] = $field['name'].' ('.\Str::of($field['id']).')'; + $field['name'] = $field['name'] . ' (' . \Str::of($field['id']) . ')'; // If not present skip - if (! isset($data[$field['id']])) { + if (!isset($data[$field['id']])) { if ($this->setEmptyForNoValue) { $returnArray[$field['name']] = ''; } @@ -128,16 +128,16 @@ class FormSubmissionFormatter } // If hide hidden fields - if (! $this->showHiddenFields) { + if (!$this->showHiddenFields) { if (FormLogicPropertyResolver::isHidden($field, $this->idFormData ?? [])) { continue; } } if ($this->createLinks && $field['type'] == 'url') { - $returnArray[$field['name']] = ''.$data[$field['id']].''; + $returnArray[$field['name']] = '' . $data[$field['id']] . ''; } elseif ($this->createLinks && $field['type'] == 'email') { - $returnArray[$field['name']] = ''.$data[$field['id']].''; + $returnArray[$field['name']] = '' . $data[$field['id']] . ''; } elseif ($field['type'] == 'multi_select') { $val = $data[$field['id']]; if ($this->outputStringsOnly && is_array($val)) { @@ -184,29 +184,31 @@ class FormSubmissionFormatter $fields = $this->form->properties; $transformedFields = []; foreach ($fields as $field) { - if (! isset($field['id']) || ! isset($data[$field['id']])) { + if (!isset($field['id']) || !isset($data[$field['id']])) { continue; } // If hide hidden fields - if (! $this->showHiddenFields) { + if (!$this->showHiddenFields) { if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) { continue; } } if ($this->createLinks && $field['type'] == 'url') { - $field['value'] = ''.$data[$field['id']].''; + $field['value'] = '' . $data[$field['id']] . ''; } elseif ($this->createLinks && $field['type'] == 'email') { - $field['value'] = ''.$data[$field['id']].''; + $field['value'] = '' . $data[$field['id']] . ''; } elseif ($field['type'] == 'checkbox') { $field['value'] = $data[$field['id']] ? 'Yes' : 'No'; } elseif ($field['type'] == 'date') { + $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); } } elseif ($field['type'] == 'multi_select') { $val = $data[$field['id']]; @@ -230,7 +232,7 @@ class FormSubmissionFormatter return [ 'unsigned_url' => route('open.forms.submissions.file', [$formId, $file]), 'signed_url' => $this->getFileUrl($formId, $file), - 'label' => \Str::limit($file, 20, '[...].'.end($splitText)), + 'label' => \Str::limit($file, 20, '[...].' . end($splitText)), ]; })->toArray(); } else { @@ -241,7 +243,6 @@ class FormSubmissionFormatter 'file_name' => $file, ]; }); - } } else { if (is_array($data[$field['id']]) && $this->outputStringsOnly) { diff --git a/client/components/forms/DateInput.vue b/client/components/forms/DateInput.vue index 44afd998..43ec951f 100644 --- a/client/components/forms/DateInput.vue +++ b/client/components/forms/DateInput.vue @@ -1,38 +1,82 @@ - + - - + - - - - - From - - - - To - - - - + + + + + + {{ formattedDatePreview }} + + + + + + + + + + + + + + @@ -40,148 +84,173 @@ - + - + + diff --git a/client/components/open/forms/OpenForm.vue b/client/components/open/forms/OpenForm.vue index 8e619a95..016ea34c 100644 --- a/client/components/open/forms/OpenForm.vue +++ b/client/components/open/forms/OpenForm.vue @@ -338,9 +338,8 @@ export default { } if (this.isPublicFormPage && this.form.editable_submissions) { - const urlParam = new URLSearchParams(window.location.search) - if (urlParam && urlParam.get('submission_id')) { - this.form.submission_id = urlParam.get('submission_id') + if (useRoute().query?.submission_id) { + this.form.submission_id = useRoute().query?.submission_id const data = await this.getSubmissionData() if (data !== null && data) { this.dataForm = useForm(data) @@ -353,15 +352,7 @@ export default { if (pendingData !== null && pendingData && Object.keys(this.pendingSubmission.get()).length !== 0) { this.fields.forEach((field) => { if (field.type === 'date' && field.prefill_today === true) { // For Prefill with 'today' - const dateObj = new Date() - let currentDate = dateObj.getFullYear() + '-' + - String(dateObj.getMonth() + 1).padStart(2, '0') + '-' + - String(dateObj.getDate()).padStart(2, '0') - if (field.with_time === true) { - currentDate += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' + - String(dateObj.getMinutes()).padStart(2, '0') - } - pendingData[field.id] = currentDate + pendingData[field.id] = new Date().toISOString() } }) this.dataForm = useForm(pendingData) @@ -395,15 +386,7 @@ export default { // Array url prefills formData[field.id] = urlPrefill.getAll(field.id + '[]') } else if (field.type === 'date' && field.prefill_today === true) { // For Prefill with 'today' - const dateObj = new Date() - let currentDate = dateObj.getFullYear() + '-' + - String(dateObj.getMonth() + 1).padStart(2, '0') + '-' + - String(dateObj.getDate()).padStart(2, '0') - if (field.with_time === true) { - currentDate += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' + - String(dateObj.getMinutes()).padStart(2, '0') - } - formData[field.id] = currentDate + formData[field.id] = new Date().toISOString() } else { // Default prefill if any formData[field.id] = field.prefill } diff --git a/client/components/open/forms/OpenFormField.vue b/client/components/open/forms/OpenFormField.vue index ad320c07..bca19e47 100644 --- a/client/components/open/forms/OpenFormField.vue +++ b/client/components/open/forms/OpenFormField.vue @@ -56,6 +56,7 @@ diff --git a/client/lib/colors.js b/client/lib/colors.js new file mode 100644 index 00000000..d0f41312 --- /dev/null +++ b/client/lib/colors.js @@ -0,0 +1,114 @@ +// 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 +export { generateColor, tailwindcssPaletteGenerator } diff --git a/client/lib/forms/form-themes.js b/client/lib/forms/form-themes.js index 2d53de8f..da38cd4f 100644 --- a/client/lib/forms/form-themes.js +++ b/client/lib/forms/form-themes.js @@ -6,11 +6,20 @@ export const themes = { default: { label: 'text-gray-700 dark:text-gray-300 font-semibold', input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-4 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100', + inputSpacing: { + vertical: 'py-2', + horizontal: 'px-4' + }, help: 'text-gray-400 dark:text-gray-500' }, Button: { body: 'transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg filter hover:brightness-110' }, + DateInput: { + label: 'text-gray-700 dark:text-gray-300 font-semibold', + input: 'rounded-lg flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100', + help: 'text-gray-400 dark:text-gray-500' + }, CodeInput: { label: 'text-gray-700 dark:text-gray-300 font-semibold', input: 'rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden', @@ -51,11 +60,20 @@ export const themes = { default: { label: 'text-gray-700 dark:text-gray-300 font-semibold', input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100', + inputSpacing: { + vertical: 'py-2', + horizontal: 'px-4' + }, help: 'text-gray-400 dark:text-gray-500' }, Button: { body: 'transition ease-in duration-200 text-center font-semibold focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110' }, + DateInput: { + label: 'text-gray-700 dark:text-gray-300 font-semibold', + input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100', + help: 'text-gray-400 dark:text-gray-500' + }, SelectInput: { label: 'text-gray-700 dark:text-gray-300 font-semibold', input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-2 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent', @@ -96,11 +114,20 @@ export const themes = { default: { label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4', input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion', + inputSpacing: { + vertical: 'py-2', + horizontal: 'px-4' + }, help: 'text-notion-input-help dark:text-gray-500' }, Button: { body: 'rounded-md transition ease-in duration-200 text-center font-semibold shadow shadow-inner-notion focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110' }, + DateInput: { + label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4', + input: 'rounded shadow-inner-notion border-transparent focus:border-transparent flex-1 appearance-none w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion p-[1px]', + help: 'text-notion-input-help dark:text-gray-500' + }, SelectInput: { label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4', input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion', diff --git a/client/package.json b/client/package.json index 3f1c4fed..2fee2f29 100644 --- a/client/package.json +++ b/client/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "build": "nuxt build", - "dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev --host=127.0.0.1", + "dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev", "generate": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare" @@ -24,8 +24,9 @@ "dependencies": { "@codemirror/lang-html": "^6.4.7", "@hcaptcha/vue3-hcaptcha": "^1.3.0", - "@nuxt/ui": "^2.14.2", + "@nuxt/ui": "^2.15.0", "@pinia/nuxt": "^0.5.1", + "@popperjs/core": "^2.11.8", "@sentry/vite-plugin": "^2.10.2", "@sentry/vue": "^7.93.0", "@vueuse/components": "^10.5.0", @@ -37,7 +38,7 @@ "clone-deep": "^4.0.1", "codemirror": "^6.0.1", "crisp-sdk-web": "^1.0.21", - "date-fns": "^2.28.0", + "date-fns": "^2.30.0", "fuse.js": "^6.4.6", "js-sha256": "^0.10.0", "libphonenumber-js": "^1.10.44", @@ -48,6 +49,7 @@ "qrcode": "^1.5.1", "query-builder-vue-3": "^1.0.1", "tinymotion": "^0.2.0", + "v-calendar": "^3.1.2", "vue": "^3.2.13", "vue-chartjs": "^5.2.0", "vue-codemirror": "^6.1.1", diff --git a/client/pages/forms/[slug]/index.vue b/client/pages/forms/[slug]/index.vue index 3bce6c10..0abff8ad 100644 --- a/client/pages/forms/[slug]/index.vue +++ b/client/pages/forms/[slug]/index.vue @@ -113,6 +113,11 @@ const loadForm = async (setup=false) => { await loadForm(true) +// Start loader if record needs to be loaded +if (useRoute().query?.submission_id) { + recordsStore.startLoading() +} + onMounted(() => { crisp.hideChat() document.body.classList.add('public-page')
- From -
- To -
+ {{ formattedDatePreview }} +