import { FC, useMemo, useRef, useState } from 'react'; import api from '../lib/api'; import { DialogWithHeader } from './Dialog'; import handleAPIError from '../lib/handleAPIError'; import List from './List'; import useActiveFetch from '../hooks/useActiveFetch'; import useChecklist from '../hooks/useChecklist'; import useConfirmDialog from '../hooks/useConfirmDialog'; import useFetch from '../hooks/useFetch'; const reduceHeader = ( header: R | ((...args: A) => R), ...args: A ): R => (typeof header === 'function' ? header(...args) : header); const CrudList = < Overview, Detail, OverviewList extends Record = Record, >( ...[props]: Parameters>> ): ReturnType>> => { const { addHeader: rAddHeader, editHeader: rEditHeader, entriesUrl, getAddLoading, getDeleteErrorMessage, getDeleteHeader, getDeletePromiseChain = (base, ...args) => base(...args), getDeleteSuccessMessage, getEditLoading = (previous?: boolean) => previous, listEmpty, listProps, onItemClick = (base, ...args) => base(...args), refreshInterval = 5000, renderAddForm, renderDeleteItem, renderEditForm, renderListItem, // Dependents entryUrlPrefix = entriesUrl, } = props; const addDialogRef = useRef(null); const editDialogRef = useRef(null); const { confirmDialog, finishConfirm, setConfirmDialogLoading, setConfirmDialogOpen, setConfirmDialogProps, } = useConfirmDialog({ initial: { scrollContent: true } }); const [edit, setEdit] = useState(false); const [entry, setEntry] = useState(); const { data: entries, mutate: refreshEntries, loading: loadingEntries, } = useFetch(entriesUrl, { refreshInterval }); const { fetch: getEntry, loading: loadingEntry } = useActiveFetch({ onData: (data) => setEntry(data), url: entryUrlPrefix, }); const addHeader = useMemo( () => reduceHeader(rAddHeader), [rAddHeader], ); const editHeader = useMemo( () => reduceHeader(rEditHeader, entry), [entry, rEditHeader], ); const formTools = useMemo( () => ({ add: { open: (v = true) => addDialogRef?.current?.setOpen(v), }, confirm: { finish: finishConfirm, loading: setConfirmDialogLoading, open: (v = true) => setConfirmDialogOpen(v), prepare: setConfirmDialogProps, }, edit: { open: (v = true) => editDialogRef?.current?.setOpen(v), }, }), [ finishConfirm, setConfirmDialogLoading, setConfirmDialogOpen, setConfirmDialogProps, ], ); const { buildDeleteDialogProps, checks, getCheck, hasAllChecks, hasChecks, multipleItems, resetChecks, setAllChecks, setCheck, } = useChecklist({ list: entries }); return ( <> allowCheckAll={multipleItems} allowEdit allowItemButton={edit} disableDelete={!hasChecks} edit={edit} getListCheckboxProps={() => ({ checked: hasAllChecks, onChange: (event, checked) => setAllChecks(checked), })} getListItemCheckboxProps={(key) => ({ checked: getCheck(key), onChange: (event, checked) => setCheck(key, checked), })} header listEmpty={listEmpty} listItems={entries} loading={loadingEntries} onAdd={() => addDialogRef?.current?.setOpen(true)} onDelete={() => { setConfirmDialogProps( buildDeleteDialogProps({ onProceedAppend: () => { setConfirmDialogLoading(true); Promise.all( getDeletePromiseChain( (cl, up) => cl.map((key) => api.delete(`${up}/${key}`)), checks, entriesUrl, ), ) .then(() => { finishConfirm('Success', getDeleteSuccessMessage()); refreshEntries(); }) .catch((error) => { const emsg = handleAPIError(error); finishConfirm('Error', getDeleteErrorMessage(emsg)); }) .finally(() => { resetChecks(); }); }, getConfirmDialogTitle: getDeleteHeader, renderEntry: (...args) => renderDeleteItem(entries, ...args), }), ); setConfirmDialogOpen(true); }} onEdit={() => setEdit((previous) => !previous)} onItemClick={(...args) => onItemClick((value, key) => { editDialogRef?.current?.setOpen(true); getEntry(`/${key}`); }, ...args) } renderListItem={renderListItem} {...listProps} /> {renderAddForm(formTools)} {renderEditForm(formTools, entry)} {confirmDialog} ); }; export default CrudList;