diff --git a/package.json b/package.json index 03e8b20c4..c30e221a8 100755 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "@testing-library/react": "^12.1.5", "@types/node": "^18.11.9", "@types/react": "^18.0.1", + "@types/validator": "^13.9.0", "@typescript-eslint/eslint-plugin": "^5.44.0", "@typescript-eslint/parser": "^5.44.0", "babel-loader": "^8.2.5", diff --git a/src/components/forms/FormSchema/FormSchema.types.ts b/src/components/forms/FormSchema/FormSchema.types.ts index 3630e3238..ead364861 100644 --- a/src/components/forms/FormSchema/FormSchema.types.ts +++ b/src/components/forms/FormSchema/FormSchema.types.ts @@ -2,100 +2,211 @@ import { RadioTypes } from 'src/components/utils/Inputs/Radio/Radio.types'; import { FilterConstant } from 'src/constants'; import { AnyCantFix, AnyToFix } from 'src/utils/Types'; -export const InputComponents = [ - 'datepicker', - 'text-input', - 'tel-input', - 'textarea', - 'checkbox', +const FormComponents = { + DATEPICKER: 'datepicker', + TEXT_INPUT: 'text-input', + TEL_INPUT: 'tel-input', + TEXTAREA: 'textarea', + CHECKBOX: 'checkbox', + SELECT_SIMPLE: 'select-simple', + SELECT: 'select', + SELECT_CREATABLE: 'select-creatable', + SELECT_ASYNC: 'select-async', + RADIO: 'radio', + RADIO_ASYNC: 'radio-async', + HEADING: 'heading', + TEXT: 'text', + FIELDGROUP: 'fieldgroup', + MULTIPLE_FIELDS: 'multiple-fields', +} as const; + +export type FormComponent = + (typeof FormComponents)[keyof typeof FormComponents]; + +export type FieldValue = + | string + | boolean + | number + | FilterConstant + | FilterConstant[]; + +type MultiFilterConstant = M extends true + ? FilterConstant[] + : FilterConstant; + +interface FormComponentValues + extends Record { + [FormComponents.DATEPICKER]: string; + [FormComponents.TEXT_INPUT]: string; + [FormComponents.TEL_INPUT]: string; + [FormComponents.TEXTAREA]: string; + [FormComponents.CHECKBOX]: boolean; + [FormComponents.SELECT_SIMPLE]: string | number; + [FormComponents.SELECT]: MultiFilterConstant; + [FormComponents.SELECT_CREATABLE]: MultiFilterConstant; + [FormComponents.SELECT_ASYNC]: MultiFilterConstant; + [FormComponents.RADIO]: string | number; + [FormComponents.RADIO_ASYNC]: string | number; +} + +export const TextInputComponents = [ + FormComponents.DATEPICKER, + FormComponents.TEXT_INPUT, + FormComponents.TEL_INPUT, + FormComponents.TEXTAREA, +] as const; + +export type TextInputComponent = (typeof TextInputComponents)[number]; + +export const CheckBoxComponents = [FormComponents.CHECKBOX] as const; + +export type CheckBoxComponent = (typeof CheckBoxComponents)[number]; +export const SelectRequestComponents = [ + FormComponents.SELECT, + FormComponents.SELECT_CREATABLE, + FormComponents.SELECT_ASYNC, ] as const; -export type InputComponent = (typeof InputComponents)[number]; +export type SelectRequestComponent = (typeof SelectRequestComponents)[number]; export const SelectComponents = [ - 'select-simple', - 'select', - 'select-creatable', - 'select-async', - 'radio', - 'radio-async', + FormComponents.SELECT_SIMPLE, + FormComponents.RADIO, + FormComponents.RADIO_ASYNC, ] as const; + export type SelectComponent = (typeof SelectComponents)[number]; -export const TextComponents = ['heading', 'text'] as const; +export const TextComponents = [ + FormComponents.HEADING, + FormComponents.TEXT, +] as const; export type TextComponent = (typeof TextComponents)[number]; -export const GroupComponents = ['fieldgroup'] as const; +export const GroupComponents = [FormComponents.FIELDGROUP] as const; export type GroupComponent = (typeof GroupComponents)[number]; -export const MultipleComponents = ['multiple-fields'] as const; +export const MultipleComponents = [FormComponents.MULTIPLE_FIELDS] as const; export type MultipleComponent = (typeof MultipleComponents)[number]; -export type FormComponent = - | InputComponent +type InputComponent = + | TextInputComponent + | CheckBoxComponent | SelectComponent - | TextComponent - | GroupComponent - | MultipleComponent; + | SelectRequestComponent; interface FormFieldCommonProperties { id: string; name: string; } -interface FormFieldInputCommonProperties extends FormFieldCommonProperties { +interface FormTypes { + test: string; +} + +export type FormType = keyof FormTypes; + +// export type GetValueType = (name: K) => FormTypes[K]; +export type GetValueType = (name: string) => AnyToFix; + +interface Rule { + method: ( + fieldValue: FormComponentValues[T], + fieldValues: { [K in FormType]: FormTypes[K] } + ) => boolean; + args?: AnyToFix[]; + message: string; +} + +interface FormFieldInputCommonProperties< + T extends InputComponent, + M extends boolean = false +> extends FormFieldCommonProperties { isRequired?: boolean; - title: - | string - | JSX.Element - | ((getValue: (name: string) => AnyToFix) => string); + rules?: Rule[]; + title: string | JSX.Element | ((getValue: GetValueType) => string); disabled?: boolean; - disable?: (getValue: (name: string) => AnyToFix) => boolean; + disable?: (getValue: GetValueType) => boolean; hidden?: boolean; - hide?: ( - getValue: (name: string) => AnyToFix, - fieldOptions?: AnyToFix - ) => boolean; + hide?: (getValue: GetValueType, fieldOptions?: AnyToFix) => boolean; placeholder?: string; showLabel?: boolean; } -export interface FormFieldInput extends FormFieldInputCommonProperties { - component: InputComponent; +export interface FormFieldTextInput + extends FormFieldInputCommonProperties { + component: TextInputComponent; type?: 'text' | 'email' | 'password'; rows?: number; maxLines?: { lines: number; width: number }; + maxLength?: number; min?: string; max?: string; } -export interface FormFieldSelect - extends FormFieldInputCommonProperties { +export interface FormFieldCheckBox + extends FormFieldInputCommonProperties { + component: CheckBoxComponent; +} + +export interface FormFieldSelect + extends FormFieldInputCommonProperties { component: SelectComponent; - dynamicFilter?: (getValue: (name: string) => AnyToFix) => string; + dynamicFilter?: (getValue: GetValueType) => string; fieldsToReset?: string[]; - options?: T; + options?: K; loadOptions?: ( callback: (options: FilterConstant[] | RadioTypes[]) => void, inputValue?: string, - getValue?: (name: string) => AnyToFix + getValue?: GetValueType ) => Promise | void; - isMulti?: boolean | ((getValue: (name: string) => AnyToFix) => boolean); - openMenuOnClick?: boolean; errorMessage?: string; limit?: number; } +export interface FormFieldSelectRequestCommon< + K extends FilterConstant[], + M extends boolean +> extends FormFieldInputCommonProperties { + component: SelectRequestComponent; + fieldsToReset?: string[]; + options?: K; + loadOptions?: ( + callback: (options: FilterConstant[] | RadioTypes[]) => void, + inputValue?: string, + getValue?: GetValueType + ) => Promise | void; + openMenuOnClick?: boolean; + isMulti?: boolean | ((getValue: (name: string) => AnyToFix) => boolean); +} + +// TODO fix type depending on is Multi +interface FormFieldSelectRequestMulti + extends FormFieldSelectRequestCommon { + isMulti?: true; +} + +interface FormFieldSelectRequestSingle + extends FormFieldSelectRequestCommon { + isMulti?: false; +} + +export type FormFieldSelectRequest = + | FormFieldSelectRequestMulti + | FormFieldSelectRequestSingle + +export type FormFieldInput = + | FormFieldTextInput + | FormFieldCheckBox + | FormFieldSelect + | FormFieldSelectRequest; export interface FormFieldText extends FormFieldCommonProperties { id: string; name: string; - title: string | ((getValue: (name: string) => AnyToFix) => string); + title: string | ((getValue: GetValueType) => string); component: TextComponent; - hide?: ( - getValue: (name: string) => AnyToFix, - fieldOptions?: AnyToFix - ) => boolean; + hide?: (getValue: GetValueType, fieldOptions?: AnyToFix) => boolean; hidden?: boolean; } @@ -104,29 +215,42 @@ export interface FormFieldMultiple extends FormFieldCommonProperties { name: string; action: string; component: MultipleComponent; - fields: (FormFieldInput | FormFieldSelect)[]; - hide?: (getValue: (name: string) => AnyToFix) => boolean; + fields: FormFieldInput[]; + hide?: (getValue: GetValueType) => boolean; hidden?: boolean; } export interface FormFieldGroup extends FormFieldCommonProperties { component: GroupComponent; - fields: (FormFieldInput | FormFieldSelect | FormFieldText)[]; - hide?: (getValue: (name: string) => AnyToFix) => boolean; + fields: (FormFieldInput | FormFieldText)[]; + hide?: (getValue: GetValueType) => boolean; hidden?: boolean; } export type FormField = | FormFieldInput - | FormFieldSelect | FormFieldText | FormFieldMultiple | FormFieldGroup; -interface FormRule {} - export interface FormSchema { id: string; fields: FormField[]; - rules: AnyToFix; // to be typed } + +const test: FormFieldSelectRequest = { + component: undefined, + id: '', + name: '', + options: undefined, + title: undefined, + isMulti: true, + rules: [ + { + method: (fieldValue) => { + return true; + }, + message: 'oui', + }, + ], +}; diff --git a/src/components/forms/FormSchema/FormSchema.utils.ts b/src/components/forms/FormSchema/FormSchema.utils.ts index b2837f342..7883bb649 100644 --- a/src/components/forms/FormSchema/FormSchema.utils.ts +++ b/src/components/forms/FormSchema/FormSchema.utils.ts @@ -1,31 +1,52 @@ import { + CheckBoxComponent, + CheckBoxComponents, FormComponent, FormField, + FormFieldCheckBox, FormFieldGroup, - FormFieldInput, FormFieldMultiple, - FormFieldSelect, + FormFieldSelect, FormFieldSelectRequest, FormFieldText, + FormFieldTextInput, GroupComponent, GroupComponents, - InputComponent, - InputComponents, MultipleComponent, MultipleComponents, SelectComponent, SelectComponents, + SelectRequestComponent, + SelectRequestComponents, TextComponent, TextComponents, -} from './FormSchema.types'; + TextInputComponent, + TextInputComponents +} from "./FormSchema.types"; -export function isFormFieldInput(field: FormField): field is FormFieldInput { - return InputComponents.includes(field.component as InputComponent); +export function isFormFieldTextInput( + field: FormField +): field is FormFieldTextInput { + return TextInputComponents.includes(field.component as TextInputComponent); +} + +export function isFormFieldCheckbox( + field: FormField +): field is FormFieldCheckBox { + return CheckBoxComponents.includes(field.component as CheckBoxComponent); } export function isFormFieldSelect(field: FormField): field is FormFieldSelect { return SelectComponents.includes(field.component as SelectComponent); } +export function isFormFieldSelectRequest( + field: FormField +): field is FormFieldSelectRequest { + return SelectRequestComponents.includes( + field.component as SelectRequestComponent + ); +} + export function isFormFieldText(field: FormField): field is FormFieldText { return TextComponents.includes(field.component as TextComponent); } diff --git a/src/components/forms/FormWithValidation.tsx b/src/components/forms/FormWithValidation.tsx index 663650a36..77f5b013f 100644 --- a/src/components/forms/FormWithValidation.tsx +++ b/src/components/forms/FormWithValidation.tsx @@ -17,13 +17,12 @@ import { ComponentException, FormSchema, isFormFieldGroup, - isFormFieldInput, + isFormFieldTextInput, isFormFieldMultiple, isFormFieldSelect, isFormFieldText, } from './FormSchema'; import { StyledForm } from './Forms.styles'; -import { reformat } from './schemas/script'; interface FormWithValidationProps { defaultValues?: AnyToFix; // to be typed @@ -53,8 +52,8 @@ export const FormWithValidation = forwardRef< }, ref ) => { - reformat(formSchema); - const { id: formId, rules, fields } = formSchema; + const { id: formId, fields } = formSchema; + const [error, setError] = useState(); const [fieldOptions, setFieldOptions] = useState({}); @@ -167,6 +166,7 @@ export const FormWithValidation = forwardRef< typeof childrenField.title === 'function' ? childrenField.title(getValues) : childrenField.title; + if (childrenField.component === 'text') { return ( FilterConstant | FilterConstant[] | string | boolean; + getValue: GetValueType; fieldOptions?: AnyToFix; updateFieldOptions?: (newFieldOption?: { [K in string]: AnyToFix }) => void; control: Control; @@ -56,20 +52,33 @@ export const GenericField = ({ resetField, watch, }: GenericFieldProps) => { + const rules = field.rules?.reduce((acc, curr, index) => { + return { + ...acc, + [`rule${index}`]: curr.method, + }; + }, {}); + const { field: { onChange, onBlur, value, name, ref }, fieldState: { error }, } = useController({ name: field.name, control, - rules: { required: !field.isRequired ? 'Obligatoire' : false }, + rules: { + required: field.isRequired ? 'Obligatoire' : false, + ...(isFormFieldTextInput(field) && field.maxLength + ? { maxLength: field.maxLength } + : {}), + validate: rules || {}, + }, }); watch(); const onChangeCustom = useCallback( (updatedValue) => { - if (isFormFieldSelect(field)) { + if (isFormFieldSelect(field) || isFormFieldSelectRequest(field)) { if (field.fieldsToReset) { for (let i = 0; i < field.fieldsToReset.length; i += 1) { resetField(field.fieldsToReset[i]); @@ -113,29 +122,33 @@ export const GenericField = ({ return null; } - if (isFormFieldInput(field)) { - if (field.component === 'datepicker') { - return ; - } + if (field.component === 'datepicker') { + return ; + } - if (field.component === 'text-input') { - return ; - } + if (field.component === 'text-input') { + return ; + } - if (field.component === 'tel-input') { - return ; - } + if (field.component === 'tel-input') { + return ; + } - if (field.component === 'textarea') { - return