[FEAT] ready gor update and suggestion

This commit is contained in:
Edouard DUPIN 2024-09-01 23:10:40 +02:00
parent 619e9aa4b8
commit 08eb0f878a
8 changed files with 152 additions and 103 deletions

View File

@ -71,17 +71,19 @@ export const AddableItem = () => {
form={form} form={form}
variableName={'data'} variableName={'data'}
addNewItem={(data: string) => { addNewItem={(data: string) => {
let upperId = 0; return new Promise((resolve, _rejects) => {
setData((previous) => { let upperId = 0;
previous.forEach((element) => { setData((previous) => {
if (element['id'] > upperId) { previous.forEach((element) => {
upperId = element['id']; if (element['id'] > upperId) {
} upperId = element['id'];
}
});
upperId++;
return [...previous, { id: upperId, name: data }];
}); });
upperId++; resolve({ id: upperId, name: data });
return [...previous, { id: upperId, name: data }];
}); });
return upperId;
}} }}
options={data} options={data}
/> />

View File

@ -26,7 +26,9 @@ export type FormSelectProps = {
// in the option specify the value field // in the option specify the value field
keyInputValue?: string; keyInputValue?: string;
// Add capability to add an item (no key but only value) // Add capability to add an item (no key but only value)
addNewItem?: (data: string) => number | string; addNewItem?: (data: string) => Promise<any>;
// if a suggestion exist at the auto compleat
suggestion?: string;
}; };
export const FormSelect = ({ export const FormSelect = ({
@ -35,8 +37,9 @@ export const FormSelect = ({
ref, ref,
placeholder, placeholder,
options, options,
keyInputKey, keyInputKey = 'id',
keyInputValue = 'name', keyInputValue = 'name',
suggestion,
addNewItem, addNewItem,
...rest ...rest
}: FormSelectProps) => { }: FormSelectProps) => {
@ -44,9 +47,8 @@ export const FormSelect = ({
const onCreate = !addNewItem const onCreate = !addNewItem
? undefined ? undefined
: (data: string) => { : (data: string) => {
const ret = addNewItem(data); addNewItem(data).then((data: object) => form.setValues({ [variableName]: data[keyInputKey] }));
form.setValues({ [variableName]: ret }); };
};
return ( return (
<FormGroup <FormGroup
isModify={form.isModify[variableName]} isModify={form.isModify[variableName]}
@ -61,6 +63,7 @@ export const FormSelect = ({
keyKey={keyInputKey} keyKey={keyInputKey}
keyValue={keyInputValue} keyValue={keyInputValue}
onCreate={onCreate} onCreate={onCreate}
suggestion={suggestion}
/> />
</FormGroup> </FormGroup>
); );

View File

@ -71,17 +71,19 @@ export const AddableItem = () => {
form={form} form={form}
variableName={'data'} variableName={'data'}
addNewItem={(data: string) => { addNewItem={(data: string) => {
let upperId = 0; return new Promise((resolve, _rejects) => {
setData((previous) => { let upperId = 0;
previous.forEach((element) => { setData((previous) => {
if (element['id'] > upperId) { previous.forEach((element) => {
upperId = element['id']; if (element['id'] > upperId) {
} upperId = element['id'];
}
});
upperId++;
return [...previous, { id: upperId, name: data }];
}); });
upperId++; resolve({ id: upperId, name: data });
return [...previous, { id: upperId, name: data }];
}); });
return upperId;
}} }}
options={data} options={data}
/> />

View File

@ -24,7 +24,7 @@ export type FormSelectMultipleProps = {
// in the option specify the value field // in the option specify the value field
keyInputValue?: string; keyInputValue?: string;
// Add capability to add an item (no key but only value) // Add capability to add an item (no key but only value)
addNewItem?: (data: string) => number | string; addNewItem?: (data: string) => Promise<any>;
}; };
export const FormSelectMultiple = ({ export const FormSelectMultiple = ({
@ -33,7 +33,7 @@ export const FormSelectMultiple = ({
ref, ref,
placeholder, placeholder,
options, options,
keyInputKey, keyInputKey = 'id',
keyInputValue = 'name', keyInputValue = 'name',
addNewItem, addNewItem,
...rest ...rest
@ -42,11 +42,8 @@ export const FormSelectMultiple = ({
const onCreate = !addNewItem const onCreate = !addNewItem
? undefined ? undefined
: (data: string) => { : (data: string) => {
const ret = addNewItem(data); addNewItem(data).then((data: object) => form.setValues({ [variableName]: [...(form.values[variableName] ?? []), data[keyInputKey]] }));
form.setValues({ };
[variableName]: [...(form.values[variableName] ?? []), ret],
});
};
return ( return (
<FormGroup <FormGroup
isModify={form.isModify[variableName]} isModify={form.isModify[variableName]}

View File

@ -1,4 +1,4 @@
import { RefObject, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { import {
Button, Button,
@ -11,7 +11,7 @@ import {
Wrap, Wrap,
WrapItem, WrapItem,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; import { MdEdit, MdKeyboardArrowDown, MdKeyboardArrowUp } 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';
@ -25,6 +25,8 @@ export type SelectMultipleProps = {
ref?: RefObject<any>; ref?: RefObject<any>;
// if set add capability to add the search item // if set add capability to add the search item
onCreate?: (data: string) => void; onCreate?: (data: string) => void;
// if a suggestion exist at the auto compleat
suggestion?: string;
}; };
export const SelectMultiple = ({ export const SelectMultiple = ({
@ -34,6 +36,7 @@ export const SelectMultiple = ({
ref, ref,
keyKey = 'id', keyKey = 'id',
keyValue = keyKey, keyValue = keyKey,
suggestion,
onCreate, onCreate,
}: SelectMultipleProps) => { }: SelectMultipleProps) => {
const [showList, setShowList] = useState(false); const [showList, setShowList] = useState(false);
@ -45,9 +48,18 @@ export const SelectMultiple = ({
} as SelectListModel; } as SelectListModel;
}); });
}, [options, keyKey, keyValue]); }, [options, keyKey, keyValue]);
const [currentSearch, setCurrentSearch] = useState<string | undefined>( const [hasSuggestion, setHasSuggestion] = useState<boolean>(
undefined onCreate && suggestion ? true : false
); );
const [currentSearch, setCurrentSearch] = useState<string | undefined>(
onCreate ? suggestion : undefined
);
useEffect(() => {
if (suggestion) {
setCurrentSearch(suggestion);
setHasSuggestion(true);
}
}, [suggestion]);
const refFocus = ref ?? useRef<HTMLInputElement | null>(null); const refFocus = ref ?? useRef<HTMLInputElement | null>(null);
const selectedOptions = useMemo(() => { const selectedOptions = useMemo(() => {
@ -76,6 +88,7 @@ export const SelectMultiple = ({
return <Spinner />; return <Spinner />;
} }
const onChangeInput = (value: string): void => { const onChangeInput = (value: string): void => {
setHasSuggestion(false);
if (value === '') { if (value === '') {
setCurrentSearch(undefined); setCurrentSearch(undefined);
} else { } else {
@ -91,9 +104,9 @@ export const SelectMultiple = ({
const createNewItem = !onCreate const createNewItem = !onCreate
? undefined ? undefined
: (data: string) => { : (data: string) => {
onCreate(data); onCreate(data);
setCurrentSearch(undefined); setCurrentSearch(undefined);
}; };
return ( return (
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="full" gap="0px">
@ -124,7 +137,7 @@ export const SelectMultiple = ({
//onSubmit={onSubmit} //onSubmit={onSubmit}
onFocus={() => setShowList(true)} onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)} onBlur={() => setTimeout(() => setShowList(false), 200)}
value={showList ? (currentSearch ?? '') : ''} value={showList ? (currentSearch ?? '') : hasSuggestion ? `suggest: ${currentSearch}` : ''}
borderRadius="5px 0 0 5px" borderRadius="5px 0 0 5px"
/> />
<Button <Button
@ -135,6 +148,8 @@ export const SelectMultiple = ({
> >
{showList ? ( {showList ? (
<MdKeyboardArrowUp color="gray.300" /> <MdKeyboardArrowUp color="gray.300" />
) : hasSuggestion ? (
<MdEdit color="gray.300" />
) : ( ) : (
<MdKeyboardArrowDown color="gray.300" /> <MdKeyboardArrowDown color="gray.300" />
)} )}

View File

@ -1,8 +1,9 @@
import { RefObject, useMemo, useRef, useState } from 'react'; import { RefObject, useEffect, 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 {
MdClose, MdClose,
MdEdit,
MdKeyboardArrowDown, MdKeyboardArrowDown,
MdKeyboardArrowUp, MdKeyboardArrowUp,
} from 'react-icons/md'; } from 'react-icons/md';
@ -19,6 +20,8 @@ export type SelectSingleProps = {
ref?: RefObject<any>; ref?: RefObject<any>;
// if set add capability to add the search item // if set add capability to add the search item
onCreate?: (data: string) => void; onCreate?: (data: string) => void;
// if a suggestion exist at the auto compleat
suggestion?: string;
}; };
export const SelectSingle = ({ export const SelectSingle = ({
@ -28,6 +31,7 @@ export const SelectSingle = ({
ref, ref,
keyKey = 'id', keyKey = 'id',
keyValue = keyKey, keyValue = keyKey,
suggestion,
onCreate, onCreate,
}: SelectSingleProps) => { }: SelectSingleProps) => {
const [showList, setShowList] = useState(false); const [showList, setShowList] = useState(false);
@ -39,9 +43,18 @@ export const SelectSingle = ({
} as SelectListModel; } as SelectListModel;
}); });
}, [options, keyKey, keyValue]); }, [options, keyKey, keyValue]);
const [currentSearch, setCurrentSearch] = useState<string | undefined>( const [hasSuggestion, setHasSuggestion] = useState<boolean>(
undefined onCreate && suggestion ? true : false
); );
const [currentSearch, setCurrentSearch] = useState<string | undefined>(
onCreate ? suggestion : undefined
);
useEffect(() => {
if (suggestion) {
setCurrentSearch(suggestion);
setHasSuggestion(true);
}
}, [suggestion]);
const refFocus = ref ?? useRef<HTMLInputElement | null>(null); const refFocus = ref ?? useRef<HTMLInputElement | null>(null);
const selectedOptions = useMemo(() => { const selectedOptions = useMemo(() => {
if (isNullOrUndefined(value)) { if (isNullOrUndefined(value)) {
@ -61,6 +74,7 @@ export const SelectSingle = ({
return <Spinner />; return <Spinner />;
} }
function onChangeInput(value: string): void { function onChangeInput(value: string): void {
setHasSuggestion(false);
if (value === '') { if (value === '') {
setCurrentSearch(undefined); setCurrentSearch(undefined);
} else { } else {
@ -68,6 +82,7 @@ export const SelectSingle = ({
} }
} }
const onRemoveItem = () => { const onRemoveItem = () => {
setHasSuggestion(false);
if (selectedOptions) { if (selectedOptions) {
if (onChange) { if (onChange) {
onChange(undefined); onChange(undefined);
@ -81,9 +96,10 @@ export const SelectSingle = ({
const createNewItem = !onCreate const createNewItem = !onCreate
? undefined ? undefined
: (data: string) => { : (data: string) => {
onCreate(data); onCreate(data);
setCurrentSearch(undefined); setCurrentSearch(undefined);
}; setHasSuggestion(false);
};
return ( return (
<Flex direction="column" width="full" gap="0px"> <Flex direction="column" width="full" gap="0px">
@ -95,7 +111,7 @@ export const SelectSingle = ({
onFocus={() => setShowList(true)} onFocus={() => setShowList(true)}
onBlur={() => setTimeout(() => setShowList(false), 200)} onBlur={() => setTimeout(() => setShowList(false), 200)}
value={ value={
showList ? (currentSearch ?? '') : (selectedOptions?.name ?? '') showList ? (currentSearch ?? '') : (selectedOptions?.name ?? hasSuggestion ? `suggest: ${currentSearch}` : '')
} }
backgroundColor={ backgroundColor={
showList || !selectedOptions ? undefined : 'green.500' showList || !selectedOptions ? undefined : 'green.500'
@ -112,6 +128,8 @@ export const SelectSingle = ({
<MdClose color="gray.300" /> <MdClose color="gray.300" />
) : showList ? ( ) : showList ? (
<MdKeyboardArrowUp color="gray.300" /> <MdKeyboardArrowUp color="gray.300" />
) : hasSuggestion ? (
<MdEdit color="gray.300" />
) : ( ) : (
<MdKeyboardArrowDown color="gray.300" /> <MdKeyboardArrowDown color="gray.300" />
)} )}

View File

@ -1,11 +1,13 @@
import { Album, AlbumResource, Artist, ArtistResource, Gender, GenderResource } from '@/back-api';
import { useFormidable } from '@/components/form/Formidable'; import { useFormidable } from '@/components/form/Formidable';
import { FormInput } from '@/components/form/FormInput'; import { FormInput } from '@/components/form/FormInput';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { PageLayout } from '@/components/Layout/PageLayout'; import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar'; import { TopBar } from '@/components/TopBar/TopBar';
import { useOrderedAlbums } from '@/service/Album'; import { useAlbumService, useOrderedAlbums } from '@/service/Album';
import { useOrderedArtists } from '@/service/Artist'; import { useArtistService, useOrderedArtists } from '@/service/Artist';
import { useGenderService, useOrderedGenders } from '@/service/Gender'; import { useGenderService, useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Flex, Text, Input, Table, TableCaption, TableContainer, Tbody, Td, Tfoot, Th, Thead, Tr, Button } from '@chakra-ui/react'; import { Flex, Text, Input, Table, TableCaption, TableContainer, Tbody, Td, Tfoot, Th, Thead, Tr, Button } from '@chakra-ui/react';
import { useState } from 'react'; import { useState } from 'react';
@ -72,6 +74,10 @@ export const AddPage = () => {
const { dataGenders } = useOrderedGenders(); const { dataGenders } = useOrderedGenders();
const { dataArtist } = useOrderedArtists(); const { dataArtist } = useOrderedArtists();
const { dataAlbums } = useOrderedAlbums(); const { dataAlbums } = useOrderedAlbums();
const { store: storeGender } = useGenderService();
const { store: storeArtist } = useArtistService();
const { store: storeAlbum } = useAlbumService();
const { session } = useServiceContext();
const updateNeedSend = () => { const updateNeedSend = () => {
if (parsedElement.length === 0) { if (parsedElement.length === 0) {
@ -270,7 +276,8 @@ export const AddPage = () => {
// add it in the list. // add it in the list.
return tmp; return tmp;
} }
const [suggestedArtist, setSuggestedArtist] = useState<string | undefined>(undefined);
const [suggestedAlbum, setSuggestedAlbum] = useState<string | undefined>(undefined);
const onChangeFile = (value: any): void => { const onChangeFile = (value: any): void => {
clearData(); clearData();
@ -286,8 +293,6 @@ export const AddPage = () => {
updateNeedSend(); updateNeedSend();
return; return;
} }
// we verify with the first value to remove all unknown ...
// clean different gender:
// clean different artist: // clean different artist:
for (let iii = 1; iii < parsedElementTmp.length; iii++) { for (let iii = 1; iii < parsedElementTmp.length; iii++) {
@ -312,24 +317,36 @@ export const AddPage = () => {
setParsedElement(parsedElementTmp); setParsedElement(parsedElementTmp);
setParsedFailedElement(parsedFailedElementTmp); setParsedFailedElement(parsedFailedElementTmp);
console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`) console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`)
setGlobalArtist(parsedElementTmp[0].artist); setGlobalArtist(parsedElementTmp[0].artist);
// find artistId: // find artistId:
console.log(`try find artist : ${parsedElementTmp[0].artist}`); console.log(`try find artist : ${parsedElementTmp[0].artist}`);
let artistFound = false;
dataArtist.forEach((data) => { dataArtist.forEach((data) => {
if (data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()) { if (data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()) {
console.log(` find artist : ${data.id}`); console.log(` find artist : ${data.id}`);
form.setValues({ artistId: data.id }); form.setValues({ artistId: data.id });
artistFound = true;
} }
}) })
if (!artistFound) {
setSuggestedArtist(parsedElementTmp[0].artist);
}
setGlobalAlbum(parsedElementTmp[0].album); setGlobalAlbum(parsedElementTmp[0].album);
// try to find album
console.log(`try find album : ${parsedElementTmp[0].album}`); console.log(`try find album : ${parsedElementTmp[0].album}`);
let albumFound = false;
dataAlbums.forEach((data) => { dataAlbums.forEach((data) => {
if (data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()) { if (data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()) {
console.log(` find album : ${data.id}`); console.log(` find album : ${data.id}`);
form.setValues({ albumId: data.id }); form.setValues({ albumId: data.id });
albumFound = true;
} }
}) })
if (!albumFound) {
setSuggestedAlbum(parsedElementTmp[0].album);
}
updateNeedSend(); updateNeedSend();
setArtistId(undefined); setArtistId(undefined);
setAlbumId(undefined); setAlbumId(undefined);
@ -521,41 +538,33 @@ export const AddPage = () => {
// }); // });
} }
const eventPopUpAlbum = (event: string): void => {
console.log(`GET event: ${event}`);
// this.popInService.close('popin-new-album');
}
const eventPopUpArtist = (event: string): void => {
console.log(`GET event: ${event}`);
//this.popInService.close('popin-new-artist');
}
const eventPopUpType = (event: string): void => {
console.log(`GET event: ${event}`);
// this.popInService.close('popin-new-type');
}
const eventPopUpPlaylist = (event: string): void => {
console.log(`GET event: ${event}`);
// this.popInService.close('popin-new-playlist');
}
const newAlbum = (): void => {
console.log('Request new Album...');
// this.popInService.open('popin-new-album');
}
const newArtist = (): void => {
console.log('Request new Artist...');
// this.popInService.open('popin-new-artist');
}
const newType = (): void => {
console.log('Request new Type...');
// this.popInService.open('popin-create-type');
}
const newPlaylist = () => {
console.log('Request new Playlist...');
// this.popInService.open('popin-new-playlist');
}
const form = useFormidable<FormInsertData>({}); const form = useFormidable<FormInsertData>({});
const addNewGender = (data: string): Promise<Gender> => {
return storeGender.update(GenderResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
}));
}
const addNewArtist = (data: string): Promise<Artist> => {
return storeArtist.update(ArtistResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
}));
}
const addNewAlbum = (data: string): Promise<Album> => {
return storeAlbum.update(AlbumResource.post({
restConfig: session.getRestConfig(),
data: {
name: data,
},
}));
}
return ( return (
<> <>
<TopBar title="Add new media" /> <TopBar title="Add new media" />
@ -584,10 +593,9 @@ export const AddPage = () => {
</Flex> </Flex>
{parsedElement && parsedElement.length !== 0 && (<> {parsedElement && parsedElement.length !== 0 && (<>
<Text fontSize="30px">Meta-data:</Text> <Text fontSize="30px">Meta-data:</Text>
<FormSelect label="Gender" form={form} variableName='genderId' options={dataGenders} keyInputValue='name' /> <FormSelect label="Gender" form={form} variableName='genderId' options={dataGenders} keyInputValue='name' addNewItem={addNewGender} />
<FormSelect label="Artist" form={form} variableName='artistId' options={dataArtist} keyInputValue='name' /> <FormSelect label="Artist" form={form} variableName='artistId' options={dataArtist} keyInputValue='name' addNewItem={addNewArtist} suggestion={suggestedArtist} />
<FormInput label="Album Title (TODO: fusion with next)" isRequired form={form} variableName='title' /> <FormSelect label="Album" form={form} variableName='albumId' options={dataAlbums} keyInputValue='name' addNewItem={addNewAlbum} suggestion={suggestedAlbum} />
<FormSelect label="Album" form={form} variableName='albumId' options={dataAlbums} keyInputValue='name' />
<TableContainer> <TableContainer>
<Table variant='striped' colorScheme='teal' background="gray.700"> <Table variant='striped' colorScheme='teal' background="gray.700">
<Thead> <Thead>
@ -605,13 +613,13 @@ export const AddPage = () => {
placeholder="e?" placeholder="e?"
value={data.trackId} value={data.trackId}
onChange={e => onTrackId(data, e.target.value)} onChange={e => onTrackId(data, e.target.value)}
//[class.error]="data.trackIdDetected === true" backgroundColor={data.trackIdDetected === true ? "darkred" : undefined}
/></Td> /></Td>
<Td><Input type="text" <Td><Input type="text"
placeholder="Name of the Media" placeholder="Name of the Media"
value={data.title} value={data.title}
onChange={e => onTitle(data, e.target.value)} onChange={e => onTitle(data, e.target.value)}
//[class.error]="data.title === ''" backgroundColor={data.title === '' ? "darkred" : undefined}
/> />
{data.nameDetected === true && {data.nameDetected === true &&
<><br /> <><br />

View File

@ -15,7 +15,7 @@ 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; update: (request: Promise<TYPE>, key?: string) => Promise<TYPE>;
remove: (id: number | string, request: Promise<void>, key?: string) => void; remove: (id: number | string, request: Promise<void>, key?: string) => void;
}; };
@ -96,19 +96,23 @@ export const useDataStore = <TYPE>(
); );
const update = useCallback( const update = useCallback(
(request: Promise<TYPE>, key?: string) => { (request: Promise<TYPE>, key?: string): Promise<TYPE> => {
const keyValue = key ?? primaryKey; const keyValue = key ?? primaryKey;
request return new Promise((resolve, rejects) => {
.then((responseData: TYPE) => { request
const filterData = data.filter( .then((responseData: TYPE) => {
(localData: TYPE) => localData[keyValue] !== responseData[keyValue] const filterData = data.filter(
); (localData: TYPE) => localData[keyValue] !== responseData[keyValue]
filterData.push(responseData); );
setData(filterData); filterData.push(responseData);
}) setData(filterData);
.catch((error: RestErrorResponse) => { resolve(responseData);
toastAPIError(error); })
}); .catch((error: RestErrorResponse) => {
toastAPIError(error);
rejects(error);
});
});
}, },
[data, setData] [data, setData]
); );