diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx index 659608b191..39a9e27514 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx @@ -20,6 +20,7 @@ import IconButton from '~components/IconButton' import { AttachmentField, CheckboxField, + DateField, DecimalField, DropdownField, EmailField, @@ -316,6 +317,8 @@ const MemoFieldRow = memo(({ field, ...rest }: MemoFieldRowProps) => { return case BasicField.Decimal: return + case BasicField.Date: + return case BasicField.Dropdown: return case BasicField.Statement: diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/EditFieldDrawer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/EditFieldDrawer.tsx index ce4e30abce..2906169c58 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/EditFieldDrawer.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/EditFieldDrawer.tsx @@ -17,9 +17,10 @@ import { } from '../../useBuilderAndDesignStore' import { CreatePageDrawerCloseButton } from '../CreatePageDrawerCloseButton' -import { EditAttachment } from './edit-fieldtype/EditAttachment' import { + EditAttachment, EditCheckbox, + EditDate, EditDecimal, EditDropdown, EditEmail, @@ -149,6 +150,8 @@ export const MemoFieldDrawerContent = memo( return case BasicField.Number: return + case BasicField.Date: + return case BasicField.Decimal: return case BasicField.Section: diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.stories.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.stories.tsx new file mode 100644 index 0000000000..4d2b34bb9c --- /dev/null +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.stories.tsx @@ -0,0 +1,78 @@ +import { Meta, Story } from '@storybook/react' + +import { + BasicField, + DateFieldBase, + DateSelectedValidation, +} from '~shared/types' + +import { EditFieldDrawerDecorator, StoryRouter } from '~utils/storybook' + +import { EditDate } from './EditDate' + +const DEFAULT_DATE_FIELD: DateFieldBase = { + title: 'Storybook Date', + description: 'Some description', + dateValidation: { + selectedDateValidation: null, + customMaxDate: null, + customMinDate: null, + }, + required: true, + disabled: false, + fieldType: BasicField.Date, + globalId: 'unused', +} + +export default { + title: 'Features/AdminForm/EditFieldDrawer/EditDate', + component: EditDate, + decorators: [ + StoryRouter({ + initialEntries: ['/61540ece3d4a6e50ac0cc6ff'], + path: '/:formId', + }), + EditFieldDrawerDecorator, + ], + parameters: { + // Required so skeleton "animation" does not hide content. + chromatic: { pauseAnimationAtEnd: true }, + }, + args: { + field: DEFAULT_DATE_FIELD, + }, +} as Meta + +interface StoryArgs { + field: DateFieldBase +} + +const Template: Story = ({ field }) => { + return +} + +export const Default = Template.bind({}) + +export const WithNoFutureDates = Template.bind({}) +WithNoFutureDates.args = { + field: { + ...DEFAULT_DATE_FIELD, + dateValidation: { + selectedDateValidation: DateSelectedValidation.NoFuture, + customMaxDate: null, + customMinDate: null, + }, + }, +} + +export const WithCustomDateRange = Template.bind({}) +WithCustomDateRange.args = { + field: { + ...DEFAULT_DATE_FIELD, + dateValidation: { + selectedDateValidation: DateSelectedValidation.Custom, + customMinDate: new Date('2020-01-01T00:00:00Z'), + customMaxDate: new Date('2020-01-12T00:00:00Z'), + }, + }, +} diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.tsx new file mode 100644 index 0000000000..6a9188be2a --- /dev/null +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDate/EditDate.tsx @@ -0,0 +1,228 @@ +import { useMemo } from 'react' +import { Controller, RegisterOptions } from 'react-hook-form' +import { Box, FormControl, SimpleGrid } from '@chakra-ui/react' +import { isBefore, isDate, isEqual } from 'date-fns' +import { extend, get, isEmpty, pick } from 'lodash' + +import { + DateFieldBase, + DateSelectedValidation, + DateValidationOptions, +} from '~shared/types/field' + +import { + transformDateToShortIsoString, + transformShortIsoStringToDate, +} from '~utils/date' +import { createBaseValidationRules } from '~utils/fieldValidation' +import DateInput from '~components/DatePicker' +import { SingleSelect } from '~components/Dropdown' +import FormErrorMessage from '~components/FormControl/FormErrorMessage' +import FormLabel from '~components/FormControl/FormLabel' +import Input from '~components/Input' +import Textarea from '~components/Textarea' +import Toggle from '~components/Toggle' + +import { DrawerContentContainer } from '../common/DrawerContentContainer' +import { FormFieldDrawerActions } from '../common/FormFieldDrawerActions' +import { EditFieldProps } from '../common/types' +import { useEditFieldForm } from '../common/useEditFieldForm' + +type EditDateProps = EditFieldProps + +const EDIT_DATE_FIELD_KEYS = ['title', 'description', 'required'] as const + +type EditDateInputs = Pick< + DateFieldBase, + typeof EDIT_DATE_FIELD_KEYS[number] +> & { + dateValidation: { + selectedDateValidation: DateSelectedValidation | '' + customMaxDate: string + customMinDate: string + } +} + +const transformDateFieldToEditForm = (field: DateFieldBase): EditDateInputs => { + const nextValidationOptions = { + selectedDateValidation: + field.dateValidation.selectedDateValidation ?? ('' as const), + customMaxDate: field.dateValidation.selectedDateValidation + ? transformDateToShortIsoString(field.dateValidation.customMaxDate) ?? '' + : ('' as const), + customMinDate: field.dateValidation.selectedDateValidation + ? transformDateToShortIsoString(field.dateValidation.customMinDate) ?? '' + : ('' as const), + } + return { + ...pick(field, EDIT_DATE_FIELD_KEYS), + dateValidation: nextValidationOptions, + } +} + +const transformDateEditFormToField = ( + inputs: EditDateInputs, + originalField: DateFieldBase, +): DateFieldBase => { + let nextValidationOptions: DateValidationOptions + switch (inputs.dateValidation.selectedDateValidation) { + case '': + nextValidationOptions = { + selectedDateValidation: null, + customMinDate: null, + customMaxDate: null, + } + break + case DateSelectedValidation.NoFuture: + case DateSelectedValidation.NoPast: + nextValidationOptions = { + selectedDateValidation: inputs.dateValidation.selectedDateValidation, + customMinDate: null, + customMaxDate: null, + } + break + case DateSelectedValidation.Custom: { + nextValidationOptions = { + selectedDateValidation: inputs.dateValidation.selectedDateValidation, + customMinDate: transformShortIsoStringToDate( + inputs.dateValidation.customMinDate, + ), + customMaxDate: transformShortIsoStringToDate( + inputs.dateValidation.customMaxDate, + ), + } + } + } + + return extend({}, originalField, inputs, { + dateValidation: nextValidationOptions, + }) +} + +export const EditDate = ({ field }: EditDateProps): JSX.Element => { + const { + register, + formState: { errors }, + getValues, + isSaveEnabled, + control, + buttonText, + handleUpdateField, + isLoading, + handleCancel, + } = useEditFieldForm({ + field, + transform: { + input: transformDateFieldToEditForm, + output: transformDateEditFormToField, + }, + }) + + const requiredValidationRule = useMemo( + () => createBaseValidationRules({ required: true }), + [], + ) + + const customMinValidationOptions: RegisterOptions< + EditDateInputs, + 'dateValidation.customMinDate' + > = useMemo( + () => ({ + // customMin is required if there is selected validation. + validate: { + hasValidation: (val) => { + const hasMaxValue = + getValues('dateValidation.selectedDateValidation') === + DateSelectedValidation.Custom && + !!getValues('dateValidation.customMaxDate') + return !!val || hasMaxValue || 'You must specify at least one date.' + }, + validDate: (val) => + !val || isDate(new Date(val)) || 'Please enter a valid date', + inRange: (val) => { + const date = new Date(val) + const maxDate = new Date(getValues('dateValidation.customMaxDate')) + return ( + isEqual(date, maxDate) || + isBefore(date, maxDate) || + 'Max date cannot be less than min date.' + ) + }, + }, + }), + [getValues], + ) + + return ( + + + Question + + {errors?.title?.message} + + + Description +