[FEAT] generize the form model

This commit is contained in:
Edouard DUPIN 2024-09-03 23:34:21 +02:00
parent 4cf71f35b6
commit 3377f80fbc
11 changed files with 275 additions and 99 deletions

View File

@ -1,12 +1,7 @@
import { ReactElement, ReactNode } from 'react'; import { ReactNode } from 'react';
import { Button, Flex, IconButton, Text, Tooltip } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import { import { MdErrorOutline, MdHelpOutline, MdRefresh } from 'react-icons/md';
MdErrorOutline,
MdHelpOutline,
MdLabelImportant,
MdRefresh,
} from 'react-icons/md';
export type FormGroupProps = { export type FormGroupProps = {
error?: ReactNode; error?: ReactNode;

View File

@ -0,0 +1,37 @@
import { RefObject } from 'react';
import { Input } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
export type FormInputProps = {
form: UseFormidableReturn;
variableName: string;
ref?: RefObject<any>;
label?: string;
placeholder?: string;
isRequired?: boolean;
};
export const FormInput = ({
form,
variableName,
ref,
placeholder,
...rest
}: FormInputProps) => {
return (
<FormGroup
isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<Input
ref={ref}
value={form.values[variableName]}
onChange={(e) => form.setValues({ [variableName]: e.target.value })}
/>
</FormGroup>
);
};

View File

@ -0,0 +1,62 @@
import { RefObject } from 'react';
import {
Input,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputProps,
NumberInputStepper,
} from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
export type FormNumberProps = Pick<
NumberInputProps,
'step' | 'defaultValue' | 'min' | 'max'
> & {
form: UseFormidableReturn;
variableName: string;
ref?: RefObject<any>;
label?: string;
placeholder?: string;
isRequired?: boolean;
};
export const FormNumber = ({
form,
variableName,
ref,
placeholder,
step,
min,
max,
defaultValue,
...rest
}: FormNumberProps) => {
return (
<FormGroup
isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<NumberInput
ref={ref}
value={form.values[variableName]}
onChange={(_, value) => form.setValues({ [variableName]: value })}
step={step}
defaultValue={defaultValue}
min={min}
max={max}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormGroup>
);
};

View File

@ -0,0 +1,42 @@
import { RefObject } from 'react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
import { SelectSingle } from '@/components/select/SelectSingle';
export type FormSelectProps = {
form: UseFormidableReturn;
variableName: string;
ref?: RefObject<any>;
label?: string;
placeholder?: string;
isRequired?: boolean;
options?: object[];
keyInputValue?: string;
};
export const FormSelect = ({
form,
variableName,
ref,
placeholder,
options,
keyInputValue = 'name',
...rest
}: FormSelectProps) => {
return (
<FormGroup
isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<SelectSingle
ref={ref}
value={form.values[variableName]}
options={options}
onChange={(value) => form.setValues({ [variableName]: value })}
keyValue={keyInputValue}
/>
</FormGroup>
);
};

View File

@ -0,0 +1,42 @@
import { RefObject } from 'react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
import { SelectMultiple } from '@/components/select/SelectMultiple';
export type FormSelectMultipleProps = {
form: UseFormidableReturn;
variableName: string;
ref?: RefObject<any>;
label?: string;
placeholder?: string;
isRequired?: boolean;
options?: object[];
keyInputValue?: string;
};
export const FormSelectMultiple = ({
form,
variableName,
ref,
placeholder,
options,
keyInputValue = 'name',
...rest
}: FormSelectMultipleProps) => {
return (
<FormGroup
isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<SelectMultiple
//ref={ref}
values={form.values[variableName]}
options={options}
onChange={(value) => form.setValues({ [variableName]: value })}
keyValue={keyInputValue}
/>
</FormGroup>
);
};

View File

@ -0,0 +1,37 @@
import { RefObject } from 'react';
import { Textarea } from '@chakra-ui/react';
import { FormGroup } from '@/components/form/FormGroup';
import { UseFormidableReturn } from '@/components/form/Formidable';
export type FormTextareaProps = {
form: UseFormidableReturn;
variableName: string;
ref?: RefObject<any>;
label?: string;
placeholder?: string;
isRequired?: boolean;
};
export const FormTextarea = ({
form,
variableName,
ref,
placeholder,
...rest
}: FormTextareaProps) => {
return (
<FormGroup
isModify={form.isModify[variableName]}
onRestore={() => form.restoreValue({ [variableName]: true })}
{...rest}
>
<Textarea
ref={ref}
value={form.values[variableName]}
onChange={(e) => form.setValues({ [variableName]: e.target.value })}
/>
</FormGroup>
);
};

View File

@ -159,3 +159,5 @@ export const useFormidable = <TYPE extends object = object>({
restoreValue, restoreValue,
}; };
}; };
export type UseFormidableReturn = ReturnType<typeof useFormidable>;

View File

@ -9,7 +9,6 @@ import {
AlertDialogOverlay, AlertDialogOverlay,
Button, Button,
UseDisclosureReturn, UseDisclosureReturn,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
export type ConfirmPopUpProps = { export type ConfirmPopUpProps = {

View File

@ -30,6 +30,11 @@ import { useNavigate, useParams } from 'react-router-dom';
import { Track, TrackResource } from '@/back-api'; import { Track, TrackResource } from '@/back-api';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
import { FormNumber } from '@/components/form/FormNumber';
import { FormSelect } from '@/components/form/FormSelect';
import { FormSelectMultiple } from '@/components/form/FormSelectMultiple';
import { FormTextarea } from '@/components/form/FormTextarea';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp'; import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { SelectMultiple } from '@/components/select/SelectMultiple'; import { SelectMultiple } from '@/components/select/SelectMultiple';
@ -142,87 +147,45 @@ export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
)} )}
{!admin && ( {!admin && (
<> <>
<FormGroup <FormInput
form={form}
variableName="name"
isRequired isRequired
isModify={form.isModify.name}
onRestore={() => form.restoreValue({ name: true })}
label="Title" label="Title"
> ref={initialRef}
<Input />
ref={initialRef} <FormTextarea
value={form.values.name} form={form}
onChange={(e) => form.setValues({ name: e.target.value })} variableName="description"
/>
</FormGroup>
<FormGroup
isModify={form.isModify.description}
onRestore={() => form.restoreValue({ description: true })}
label="Description" label="Description"
> />
<Textarea <FormSelect
value={form.values.description} form={form}
onChange={(e) => variableName="genderId"
form.setValues({ description: e.target.value }) options={dataGenders}
}
/>
</FormGroup>
<FormGroup
isModify={form.isModify.genderId}
onRestore={() => form.restoreValue({ genderId: true })}
label="Gender" label="Gender"
> />
<SelectSingle <FormSelectMultiple
value={form.values.genderId} form={form}
options={dataGenders} variableName="artists"
onChange={(value) => form.setValues({ genderId: value })} options={dataArtist}
keyValue="name"
/>
</FormGroup>
<FormGroup
isModify={form.isModify.artists}
onRestore={() => form.restoreValue({ artists: true })}
label="Artist(s)" label="Artist(s)"
> />
<SelectMultiple <FormSelect
values={form.values.artists} form={form}
options={dataArtist} variableName="albumId"
onChange={(value) => form.setValues({ artists: value })} options={dataAlbums}
keyValue="name"
/>
</FormGroup>
<FormGroup
isModify={form.isModify.albumId}
onRestore={() => form.restoreValue({ albumId: true })}
label="Album" label="Album"
> />
<SelectSingle <FormNumber
value={form.values.albumId} form={form}
options={dataAlbums} variableName="track"
onChange={(value) => form.setValues({ albumId: value })}
keyValue="name"
/>
</FormGroup>
<FormGroup
isModify={form.isModify.track}
onRestore={() => form.restoreValue({ track: true })}
label="Track n°" label="Track n°"
> step={1}
<NumberInput defaultValue={0}
value={form.values.track} min={0}
onChange={(_, value) => form.setValues({ track: value })} max={1000}
step={1} />
defaultValue={0}
min={0}
max={1000}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormGroup>
</> </>
)} )}
</ModalBody> </ModalBody>

View File

@ -1,11 +1,9 @@
import { useMemo, useRef, useState } from 'react'; import { RefObject, useMemo, useRef, useState } from 'react';
import { import {
Button, Button,
Flex, Flex,
Input, Input,
InputGroup,
InputRightElement,
Spinner, Spinner,
Tag, Tag,
TagCloseButton, TagCloseButton,
@ -13,12 +11,7 @@ import {
Wrap, Wrap,
WrapItem, WrapItem,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import { MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md';
MdClose,
MdKeyboardArrowDown,
MdKeyboardArrowUp,
MdSearch,
} from 'react-icons/md';
import { SelectList, SelectListModel } from '@/components/select/SelectList'; import { SelectList, SelectListModel } from '@/components/select/SelectList';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
@ -29,12 +22,14 @@ export type SelectMultipleProps = {
onChange?: (value: (number | string)[] | undefined) => void; onChange?: (value: (number | string)[] | undefined) => void;
keyKey?: string; keyKey?: string;
keyValue?: string; keyValue?: string;
ref?: RefObject<any>;
}; };
export const SelectMultiple = ({ export const SelectMultiple = ({
options, options,
onChange, onChange,
values, values,
ref,
keyKey = 'id', keyKey = 'id',
keyValue = keyKey, keyValue = keyKey,
}: SelectMultipleProps) => { }: SelectMultipleProps) => {
@ -51,7 +46,7 @@ export const SelectMultiple = ({
undefined undefined
); );
const ref = useRef<HTMLInputElement | null>(null); const refFocus = ref ?? useRef<HTMLInputElement | null>(null);
const selectedOptions = useMemo(() => { const selectedOptions = useMemo(() => {
if (isNullOrUndefined(values) || !transformedOption) { if (isNullOrUndefined(values) || !transformedOption) {
return []; return [];
@ -86,7 +81,7 @@ export const SelectMultiple = ({
}; };
const onOpenClose = () => { const onOpenClose = () => {
if (!showList) { if (!showList) {
ref?.current?.focus(); refFocus?.current?.focus();
return; return;
} }
}; };
@ -114,7 +109,7 @@ export const SelectMultiple = ({
<Flex> <Flex>
<Input <Input
ref={ref} ref={refFocus}
width="full" width="full"
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit} //onSubmit={onSubmit}

View File

@ -1,4 +1,4 @@
import { useMemo, useRef, useState } from 'react'; import { RefObject, useMemo, useRef, useState } from 'react';
import { Button, Flex, Input, Spinner } from '@chakra-ui/react'; import { Button, Flex, Input, Spinner } from '@chakra-ui/react';
import { import {
@ -16,12 +16,14 @@ export type SelectSingleProps = {
onChange?: (value: number | string | undefined) => void; onChange?: (value: number | string | undefined) => void;
keyKey?: string; keyKey?: string;
keyValue?: string; keyValue?: string;
ref?: RefObject<any>;
}; };
export const SelectSingle = ({ export const SelectSingle = ({
options, options,
onChange, onChange,
value, value,
ref,
keyKey = 'id', keyKey = 'id',
keyValue = keyKey, keyValue = keyKey,
}: SelectSingleProps) => { }: SelectSingleProps) => {
@ -37,7 +39,7 @@ export const SelectSingle = ({
const [currentSearch, setCurrentSearch] = useState<string | undefined>( const [currentSearch, setCurrentSearch] = useState<string | undefined>(
undefined undefined
); );
const ref = useRef<HTMLInputElement | null>(null); const refFocus = ref ?? useRef<HTMLInputElement | null>(null);
const selectedOptions = useMemo(() => { const selectedOptions = useMemo(() => {
if (isNullOrUndefined(value)) { if (isNullOrUndefined(value)) {
return undefined; return undefined;
@ -69,7 +71,7 @@ export const SelectSingle = ({
} }
} }
if (!showList || selectedOptions) { if (!showList || selectedOptions) {
ref?.current?.focus(); refFocus?.current?.focus();
} }
}; };
@ -77,7 +79,7 @@ export const SelectSingle = ({
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="full" gap="0px">
<Flex> <Flex>
<Input <Input
ref={ref} ref={refFocus}
width="full" width="full"
onChange={(e) => onChangeInput(e.target.value)} onChange={(e) => onChangeInput(e.target.value)}
//onSubmit={onSubmit} //onSubmit={onSubmit}