[FEAT] live edit, remove is operational with confirm pop-in.

need to upgrade the code to be reusable
This commit is contained in:
Edouard DUPIN 2024-09-03 00:23:49 +02:00
parent 0687369164
commit faaf3b280b
8 changed files with 463 additions and 203 deletions

View File

@ -1,18 +1,21 @@
import { ReactNode } from 'react'; import { ReactElement, ReactNode } from 'react';
import { Button, Flex, IconButton, Text, Tooltip } from '@chakra-ui/react';
import { import {
FormControl, MdErrorOutline,
FormControlProps, MdHelpOutline,
FormErrorMessage, MdLabelImportant,
FormHelperText, MdRefresh,
FormLabel, } from 'react-icons/md';
} from '@chakra-ui/react';
import { MdLabelImportant } from 'react-icons/md';
export type FormGroupProps = FormControlProps & { export type FormGroupProps = {
error?: ReactNode; error?: ReactNode;
help?: ReactNode; help?: ReactNode;
label?: ReactNode; label?: ReactNode;
isModify?: boolean;
onRestore?: () => void;
isRequired?: boolean;
children: ReactNode;
}; };
export const FormGroup = ({ export const FormGroup = ({
@ -20,16 +23,45 @@ export const FormGroup = ({
error, error,
help, help,
label, label,
...props isModify = false,
isRequired = false,
onRestore,
}: FormGroupProps) => ( }: FormGroupProps) => (
<FormControl marginTop="4px" {...props}> <Flex
{!!label && <FormLabel>{label}</FormLabel>} borderLeftWidth="3px"
borderLeftColor={error ? 'red' : isModify ? 'blue' : '#00000000'}
paddingLeft="7px"
paddingY="4px"
direction="column"
>
<Flex direction="row" width="full" gap="52px">
{!!label && (
<Text marginRight="auto" fontWeight="bold">
{label}{' '}
{isRequired && (
<Text as="span" color="red.600">
*
</Text>
)}
</Text>
)}
{!!onRestore && isModify && (
<MdRefresh size="15px" onClick={onRestore} cursor="pointer" />
)}
</Flex>
{children} {children}
{!!help && <FormHelperText>{help}</FormHelperText>} {!!help && (
<Flex direction="row">
<MdHelpOutline />
<Text>{help}</Text>
</Flex>
)}
<FormErrorMessage> {!!error && (
<MdLabelImportant /> <Flex direction="row">
{error} <MdErrorOutline />
</FormErrorMessage> <Text>{error}</Text>
</FormControl> </Flex>
)}
</Flex>
); );

View File

@ -1,3 +1,5 @@
import { useEffect, useRef } from 'react';
import { Box, Button, Flex, Text } from '@chakra-ui/react'; import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
@ -45,7 +47,15 @@ export const FormSelectList = ({
search, search,
}: FormSelectListProps) => { }: FormSelectListProps) => {
const displayedValue = optionToOptionDisplay(options, selected, search); const displayedValue = optionToOptionDisplay(options, selected, search);
const scrollToRef = useRef<HTMLButtonElement | null>(null);
useEffect(() => {
if (scrollToRef?.current) {
scrollToRef?.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}, []);
return ( return (
<Box position="relative"> <Box position="relative">
<Flex <Flex
@ -75,6 +85,7 @@ export const FormSelectList = ({
backgroundColor={data.isSelected ? 'green.800' : '0x00000000'} backgroundColor={data.isSelected ? 'green.800' : '0x00000000'}
_hover={{ backgroundColor: 'gray.400' }} _hover={{ backgroundColor: 'gray.400' }}
onClick={() => onSelectValue(data)} onClick={() => onSelectValue(data)}
ref={data.isSelected ? scrollToRef : undefined}
> >
<Text marginRight="auto" autoFocus={false}> <Text marginRight="auto" autoFocus={false}>
{data.name} {data.name}

View File

@ -1,14 +1,6 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
const getDifferences = (obj1: any, obj2: any): { [key: string]: boolean } => { import { isArray, isNullOrUndefined, isObject } from '@/utils/validator';
const result: { [key: string]: boolean } = {};
for (const key in obj1) {
if (obj1.hasOwnProperty(key)) {
result[key] = obj1[key] !== obj2[key];
}
}
return result;
};
const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => { const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
for (const key in obj) { for (const key in obj) {
@ -19,55 +11,153 @@ const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
return false; return false;
}; };
// const onChangeValue = (key: string, data) => { function getDifferences(
// console.log(`[${key}] data: ${data}`); obj1: object,
// setNewData((previous) => { obj2: object
// if (previous === undefined) { ): { [key: string]: boolean } {
// return previous; // Create an empty object to store the differences
// } const result: { [key: string]: boolean } = {};
// if (previous[key] === data) { // Recursive function to compare values
// return previous; function compareValues(value1: any, value2: any): boolean {
// } // If both values are objects, compare their properties recursively
// const tmp = { ...previous }; if (isObject(value1) && isObject(value2)) {
// tmp[key] = data; return hasAnyTrue(getDifferences(value1, value2));
// console.log(`data = ${JSON.stringify(tmp, null, 2)}`); }
// return tmp; // If both values are arrays, compare their elements
// }); if (isArray(value1) && isArray(value2)) {
// }; //console.log(`Check is array: ${JSON.stringify(value1)} =?= ${JSON.stringify(value2)}`);
if (value1.length !== value2.length) {
return true;
}
for (let i = 0; i < value1.length; i++) {
if (compareValues(value1[i], value2[i])) {
return true;
}
}
return false;
}
// Otherwise, compare the values directly
//console.log(`compare : ${value1} =?= ${value2}`);
return value1 !== value2;
}
export const useFormidable = <TYPE = object,>({ // Get all keys from both objects
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
// Iterate over all keys
for (const key of allKeys) {
if (compareValues(obj1[key], obj2[key])) {
result[key] = true;
} else {
result[key] = false;
}
}
return result;
}
export const useFormidable = <TYPE extends object = object>({
initialValues = {} as TYPE, initialValues = {} as TYPE,
}: { }: {
initialValues?: TYPE; initialValues?: TYPE;
}) => { }) => {
const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE); const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE);
const initialData = useMemo<TYPE>(() => { const [initialData, setInitialData] = useState<TYPE>(initialValues);
setValues({ ...initialValues } as TYPE);
return { ...initialValues } as TYPE;
}, [initialValues, setValues]);
const [valueChange, setValueChange] = useState<{ [key: string]: boolean }>( const [valueChange, setValueChange] = useState<{ [key: string]: boolean }>(
{} {}
); );
const [isFormModified, setIsFormModified] = useState<boolean>(false); const [isFormModified, setIsFormModified] = useState<boolean>(false);
useEffect(() => {
setInitialData((previous) => {
//console.log(`FORMIDABLE: useMemo initial Values(${JSON.stringify(initialValues)})`);
const previousJson = JSON.stringify(previous);
const newJson = JSON.stringify(initialValues);
if (previousJson === newJson) {
return previous;
}
//console.log(`FORMIDABLE: ==> update new values`);
setValues({ ...initialValues });
const ret = getDifferences(initialValues, initialValues);
setValueChange(ret);
setIsFormModified(hasAnyTrue(ret));
return initialValues;
});
}, [
initialValues,
setInitialData,
setValues,
setValueChange,
setIsFormModified,
]);
const setValuesExternal = useCallback( const setValuesExternal = useCallback(
(data: object) => { (data: object) => {
//console.log(`FORMIDABLE: setValuesExternal(${JSON.stringify(data)}) ==> keys=${Object.keys(data)}`);
setValues((previous) => { setValues((previous) => {
const newValues = { ...previous, ...data }; const newValues = { ...previous, ...data };
const ret = getDifferences(initialData, values); const ret = getDifferences(initialData, newValues);
setValueChange(ret); setValueChange(ret);
setIsFormModified(hasAnyTrue(ret)); setIsFormModified(hasAnyTrue(ret));
console.log(
` ppppppppppppppppppp ${JSON.stringify(ret, null, 2)} ==> ${hasAnyTrue(ret)}`
);
return newValues; return newValues;
}); });
}, },
[setValues, initialData] [setValues, initialData]
); );
const restoreValue = useCallback(
(data: object) => {
setValues((previous) => {
const keysInPrevious = Object.keys(previous);
const newValue = { ...previous };
let countModify = 0;
//console.log(`restore value ${JSON.stringify(data, null, 2)}`);
for (const key of Object.keys(data)) {
if (!keysInPrevious.includes(key)) {
continue;
}
if (data[key] === false) {
continue;
}
newValue[key] = initialValues[key];
countModify++;
}
if (countModify === 0) {
return previous;
}
//console.log(`initialData data ${JSON.stringify(initialData, null, 2)}`);
//console.log(`New data ${JSON.stringify(newValue, null, 2)}`);
const ret = getDifferences(initialData, newValue);
setValueChange(ret);
setIsFormModified(hasAnyTrue(ret));
return newValue;
});
},
[setValues, initialData, setIsFormModified, setValueChange]
);
const getDeltaData = useCallback(
({ omit = [], only }: { omit?: string[]; only?: string[] }) => {
const out = {};
Object.keys(valueChange).forEach((key) => {
if (omit.includes(key) || (only && !only.includes(key))) {
return;
}
if (!valueChange[key]) {
return;
}
const tmpValue = values[key];
if (isNullOrUndefined(tmpValue)) {
out[key] = null;
} else {
out[key] = tmpValue;
}
});
return out;
},
[valueChange, values]
);
return { return {
values, values,
valueChange, valueChange,
isFormModified, isFormModified,
setValues: setValuesExternal, setValues: setValuesExternal,
getDeltaData,
restoreValue,
}; };
}; };

View File

@ -60,7 +60,7 @@ export const SelectMultiple = ({
const selectValue = (data: SelectMultipleValueDisplayType) => { const selectValue = (data: SelectMultipleValueDisplayType) => {
const newValues = values?.includes(data.id) const newValues = values?.includes(data.id)
? values.filter((elem) => data.id === elem) ? values.filter((elem) => data.id !== elem)
: [...(values ?? []), data.id]; : [...(values ?? []), data.id];
setShowList(false); setShowList(false);
if (onChange) { if (onChange) {

View File

@ -0,0 +1,61 @@
import { useRef } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
UseDisclosureReturn,
useDisclosure,
} from '@chakra-ui/react';
export type ConfirmPopUpProps = {
title: string;
body: string;
confirmTitle: string;
onConfirm: () => void;
disclosure: UseDisclosureReturn;
};
export const ConfirmPopUp = ({
title,
body,
confirmTitle,
onConfirm,
disclosure,
}: ConfirmPopUpProps) => {
const onClickConfirm = () => {
onConfirm();
disclosure.onClose();
};
const cancelRef = useRef(null);
return (
<AlertDialog
isOpen={disclosure.isOpen}
leastDestructiveRef={cancelRef}
onClose={disclosure.onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
</AlertDialogHeader>
<AlertDialogBody>{body}</AlertDialogBody>
<AlertDialogFooter>
<Button onClick={disclosure.onClose} ref={cancelRef}>
Cancel
</Button>
<Button colorScheme="red" onClick={onClickConfirm} ml={3}>
{confirmTitle}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
);
};

View File

@ -2,8 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import { import {
Button, Button,
FormControl, IconButton,
FormLabel,
Input, Input,
Modal, Modal,
ModalBody, ModalBody,
@ -12,205 +11,246 @@ import {
ModalFooter, ModalFooter,
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput, NumberInput,
NumberInputField,
NumberInputStepper,
Text,
Textarea, Textarea,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { Formiz, useForm, useFormFields } from '@formiz/core'; import {
MdAdminPanelSettings,
MdDeleteForever,
MdEdit,
MdRemove,
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Album, Track } from '@/back-api'; import { Track, TrackResource } from '@/back-api';
import { FormGroup } from '@/components/form/FormGroup'; import { FormGroup } from '@/components/form/FormGroup';
import { useFormidable } from '@/components/form/Formidable';
import { SelectMultiple } from '@/components/form/SelectMultiple'; import { SelectMultiple } from '@/components/form/SelectMultiple';
import { SelectSingle } from '@/components/form/SelectSingle'; import { SelectSingle } from '@/components/form/SelectSingle';
import { ConfirmPopUp } from '@/components/popup/ConfirmPopUp';
import { useOrderedAlbums } from '@/service/Album'; import { useOrderedAlbums } from '@/service/Album';
import { useOrderedArtists } from '@/service/Artist'; import { useOrderedArtists } from '@/service/Artist';
import { useOrderedGenders } from '@/service/Gender'; import { useOrderedGenders } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track'; import { useServiceContext } from '@/service/ServiceContext';
import { useSpecificTrack, useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
export type TrackEditPopUpProps = {}; export type TrackEditPopUpProps = {};
const getDifferences = (obj1: any, obj2: any): { [key: string]: boolean } => {
const result: { [key: string]: boolean } = {};
for (const key in obj1) {
if (obj1.hasOwnProperty(key)) {
result[key] = obj1[key] !== obj2[key];
}
}
return result;
};
const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
for (const key in obj) {
if (obj.hasOwnProperty(key) && obj[key] === true) {
return true;
}
}
return false;
};
export const TrackEditPopUp = ({}: TrackEditPopUpProps) => { export const TrackEditPopUp = ({}: TrackEditPopUpProps) => {
const { trackId } = useParams(); const { trackId } = useParams();
const trackIdInt = isNullOrUndefined(trackId)
? undefined
: parseInt(trackId, 10);
const { session } = useServiceContext();
const { dataGenders } = useOrderedGenders(undefined); const { dataGenders } = useOrderedGenders(undefined);
const { dataArtist } = useOrderedArtists(undefined); const { dataArtist } = useOrderedArtists(undefined);
const { dataAlbums } = useOrderedAlbums(undefined); const { dataAlbums } = useOrderedAlbums(undefined);
const { dataTrack } = useSpecificTrack( const { store } = useTrackService();
isNullOrUndefined(trackId) ? undefined : parseInt(trackId, 10) const { dataTrack } = useSpecificTrack(trackIdInt);
); const [admin, setAdmin] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const disclosure = useDisclosure();
const onClose = () => { const onClose = () => {
navigate('../../', { relative: 'path' }); navigate('../../', { relative: 'path' });
}; };
const onSubmit = (values) => { const onRemove = () => {
console.log(`onSubmit = ${values}`); if (isNullOrUndefined(trackIdInt)) {
}; return;
const onValuesChange = (values, form) => { }
console.log(`onValuesChange = ${values}`); store.remove(
trackIdInt,
TrackResource.remove({
restConfig: session.getRestConfig(),
params: {
id: trackIdInt,
},
})
);
onClose();
}; };
const initialRef = useRef(null); const initialRef = useRef(null);
const finalRef = useRef(null); const finalRef = useRef(null);
const form = useForm<Track>({ const form = useFormidable<Track>({
onSubmit, //onSubmit,
onValuesChange, //onValuesChange,
initialValues: { ...dataTrack }, initialValues: dataTrack,
onValid: () => console.log('onValid'), //onValid: () => console.log('onValid'),
onInvalid: () => console.log('onInvalid'), //onInvalid: () => console.log('onInvalid'),
}); });
const formValues = useFormFields({ const onSave = async () => {
connect: form, if (isNullOrUndefined(trackIdInt)) {
selector: (field) => field.value,
});
/*
const formValues = useFormFields({
connect: form,
fields: ['genderId'] as const,
selector: 'value',
});
*/
console.log(`dataTrack : ${JSON.stringify(dataTrack, null, 2)}`);
console.log(`Redraw : ${JSON.stringify(formValues, null, 2)}`);
const [newData, setNewData] = useState<Track>();
const [changeData, setChangeData] = useState<{ [key: string]: boolean }>();
const [changeOneData, setChangeOneData] = useState<boolean>(true);
// initialize value when ready
useEffect(() => {
setNewData(dataTrack);
}, [dataTrack, setNewData, setChangeData]);
useEffect(() => {
if (!newData) {
return; return;
} }
const ret = getDifferences(dataTrack, newData); const dataThatNeedToBeUpdated = form.getDeltaData({ omit: ['covers'] });
setChangeData(ret); console.log(`onSave = ${JSON.stringify(dataThatNeedToBeUpdated, null, 2)}`);
///////////////////////setChangeOneData(hasAnyTrue(ret)); store.update(
console.log( TrackResource.patch({
`ChangeData=${JSON.stringify(ret, null, 2)} ChangeOneData=${hasAnyTrue(ret)}` restConfig: session.getRestConfig(),
data: dataThatNeedToBeUpdated,
params: {
id: trackIdInt,
},
})
); );
}, [dataTrack, newData, setChangeData, setChangeOneData]); };
// const onChangeValue = (key: string, data) => {
// console.log(`[${key}] data: ${data}`);
// setNewData((previous) => {
// if (previous === undefined) {
// return previous;
// }
// if (previous[key] === data) {
// return previous;
// }
// const tmp = { ...previous };
// tmp[key] = data;
// console.log(`data = ${JSON.stringify(tmp, null, 2)}`);
// return tmp;
// });
// };
return ( return (
<Formiz connect={form} autoForm> <Modal
<form noValidate onSubmit={form.submit}> initialFocusRef={initialRef}
<Modal finalFocusRef={finalRef}
initialFocusRef={initialRef} closeOnOverlayClick={false}
finalFocusRef={finalRef} onClose={onClose}
closeOnOverlayClick={false} isOpen={true}
onClose={onClose} >
isOpen={true} <ModalOverlay />
> <ModalContent>
<ModalOverlay /> <ModalHeader>Edit Track</ModalHeader>
<ModalContent> <ModalCloseButton ref={finalRef} />
<ModalHeader>Edit Track</ModalHeader>
<ModalCloseButton ref={finalRef} />
<ModalBody pb={6}> <ModalBody pb={6} gap="0px" paddingLeft="18px">
<FormGroup isRequired> {admin && (
<FormLabel>Title</FormLabel> <>
<FormGroup isRequired label="Id">
<Text>{dataTrack?.id}</Text>
</FormGroup>
<FormGroup label="Data Id">
<Text>{dataTrack?.dataId}</Text>
</FormGroup>
<FormGroup label="Action(s):">
<Button
onClick={disclosure.onOpen}
marginRight="auto"
variant="@danger"
>
<MdDeleteForever /> Remove Media
</Button>
</FormGroup>
<ConfirmPopUp
disclosure={disclosure}
title="Remove track"
body={`Remove Media [${dataTrack?.id}] ${dataTrack?.name}`}
confirmTitle="Remove"
onConfirm={onRemove}
/>
</>
)}
{!admin && (
<>
<FormGroup
isRequired
isModify={form.valueChange.name}
onRestore={() => form.restoreValue({ name: true })}
label="Title"
>
<Input <Input
ref={initialRef} ref={initialRef}
value={formValues.name} value={form.values.name}
onChange={(e) => form.setValues({ name: e.target.value })} onChange={(e) => form.setValues({ name: e.target.value })}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup
<label>Description</label> isModify={form.valueChange.description}
onRestore={() => form.restoreValue({ description: true })}
label="Description"
>
<Textarea <Textarea
value={formValues.description} value={form.values.description}
onChange={(e) => onChange={(e) =>
form.setValues({ description: e.target.value }) form.setValues({ description: e.target.value })
} }
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup
<label>Gender</label> isModify={form.valueChange.genderId}
onRestore={() => form.restoreValue({ genderId: true })}
label="Gender"
>
<SelectSingle <SelectSingle
value={formValues.genderId} value={form.values.genderId}
options={dataGenders} options={dataGenders}
onChange={(value) => { onChange={(value) => form.setValues({ genderId: value })}
form.setValues({ genderId: value });
console.log(`event change value: ${value}`);
console.log(
`values: ${JSON.stringify(formValues, null, 2)}`
);
}}
keyValue="name" keyValue="name"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup
<label>Artist(s)</label> isModify={form.valueChange.artists}
onRestore={() => form.restoreValue({ artists: true })}
label="Artist(s)"
>
<SelectMultiple <SelectMultiple
values={formValues.artists} values={form.values.artists}
options={dataArtist} options={dataArtist}
onChange={(value) => form.setValues({ artists: value })} onChange={(value) => form.setValues({ artists: value })}
keyValue="name" keyValue="name"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup
<label>Album</label> isModify={form.valueChange.albumId}
onRestore={() => form.restoreValue({ albumId: true })}
label="Album"
>
<SelectSingle <SelectSingle
value={formValues.albumId} value={form.values.albumId}
options={dataAlbums} options={dataAlbums}
onChange={(value) => form.setValues({ albumId: value })} onChange={(value) => form.setValues({ albumId: value })}
keyValue="name" keyValue="name"
/> />
</FormGroup> </FormGroup>
<FormControl> <FormGroup
<FormLabel>track n°</FormLabel> isModify={form.valueChange.track}
onRestore={() => form.restoreValue({ track: true })}
label="Track n°"
>
<NumberInput <NumberInput
value={formValues.track} value={form.values.track}
onChange={(value) => form.setValues({ track: value })} onChange={(_, value) => form.setValues({ track: value })}
/> step={1}
</FormControl> defaultValue={0}
</ModalBody> min={0}
<ModalFooter> max={1000}
{changeOneData && ( >
<Button colorScheme="blue" mr={3} type="submit"> <NumberInputField />
Save <NumberInputStepper>
</Button> <NumberIncrementStepper />
)} <NumberDecrementStepper />
<Button onClick={onClose}>Cancel</Button> </NumberInputStepper>
</ModalFooter> </NumberInput>
</ModalContent> </FormGroup>
</Modal> </>
</form> )}
</Formiz> </ModalBody>
<ModalFooter>
<Button
onClick={() => setAdmin((value) => !value)}
marginRight="auto"
>
{admin ? (
<>
<MdEdit />
Edit
</>
) : (
<>
<MdAdminPanelSettings />
Admin
</>
)}
</Button>
{!admin && form.isFormModified && (
<Button colorScheme="blue" mr={3} onClick={onSave}>
Save
</Button>
)}
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
); );
}; };

View File

@ -70,9 +70,9 @@ export default defineStyleConfig({
'@danger': (props) => '@danger': (props) =>
customVariant({ customVariant({
theme: props.theme, theme: props.theme,
bg: mode('error.600', 'error.300')(props), bg: mode('error.600', 'error.600')(props),
bgHover: mode('error.700', 'error.400')(props), bgHover: mode('error.700', 'error.500')(props),
bgActive: mode('error.600', 'error.300')(props), bgActive: mode('error.600', 'error.500')(props),
color: mode('white', 'error.900')(props), color: mode('white', 'error.900')(props),
boxColorFocus: mode('error.900', 'error.500')(props), boxColorFocus: mode('error.900', 'error.500')(props),
}), }),

View File

@ -14,6 +14,8 @@ export type DataStoreType<TYPE> = {
data: TYPE[]; data: TYPE[];
get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined; get: <MODEL>(value: MODEL, key?: string) => TYPE | undefined;
gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[]; gets: <MODEL>(value: MODEL[] | undefined, key?: string) => TYPE[];
update: (request: Promise<TYPE>, key?: string) => void;
remove: (id: number | string, request: Promise<void>, key?: string) => void;
}; };
export const useDataStore = <TYPE>( export const useDataStore = <TYPE>(
@ -85,16 +87,40 @@ export const useDataStore = <TYPE>(
); );
const update = useCallback( const update = useCallback(
(value: TYPE, key?: string) => { (request: Promise<TYPE>, key?: string) => {
const keyValue = key ?? primaryKey; const keyValue = key ?? primaryKey;
const filterData = data.filter( request
(localData: TYPE) => localData[keyValue] === value[keyValue] .then((responseData: TYPE) => {
); const filterData = data.filter(
filterData.push(value); (localData: TYPE) => localData[keyValue] !== responseData[keyValue]
setData(filterData); );
filterData.push(responseData);
setData(filterData);
})
.catch((error) => {
console.log(`catch an error: ... ${JSON.stringify(error, null, 2)}`);
});
},
[data, setData]
);
const remove = useCallback(
(id: number | string, request: Promise<void>, key?: string) => {
const keyValue = key ?? primaryKey;
request
.then(() => {
const filterData = data.filter(
(localData: TYPE) => localData[keyValue] !== id
);
setData(filterData);
})
.catch((error) => {
console.log(
`catch an error on delete: ... ${JSON.stringify(error, null, 2)}`
);
});
}, },
[data, setData] [data, setData]
); );
return { isLoading, error, data, get, gets }; //, update}; return { isLoading, error, data, get, gets, update, remove };
}; };