import { Field } from './Field'; import { AddTableRow } from './AddTableRow'; import { TableCreateButton } from './TableCreateButton'; import { EditButton } from './EditButton'; import { ActionsMenu } from '@/components/ActionsMenu'; import { TableFormIteratorContext } from '@/components/ra-forms/TableForm/TableFormIteratorContext'; import { TableFormIteratorItem } from '@/components/ra-forms/TableForm/TableFormIteratorItem'; import { Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; import { FormDataConsumer, RaRecord, useTranslate, useTranslateLabel } from 'ra-core'; import * as React from 'react'; import { Children, ReactElement, ReactNode, useCallback, useMemo, useRef } from 'react'; import { useArrayInput } from 'react-admin'; import { UseFieldArrayReturn, useFormContext } from 'react-hook-form'; import { Tooltip } from '@/components/@extended'; import { DeleteWithConfirmButton } from '@/components/ra-buttons'; /** * How to use TableFormIterator: * * @example * * * * * * * * {...} * * * * */ function RawTableFormIterator(props: TableFormIteratorProps): ReactElement | null { const { children, resource, source, label, disableAdd = false, disableRemove = false, enableClearAll = false, className, empty, template = {}, addButton = , inset, sx } = props; const { fields, remove, replace, append } = useArrayInput(props); const { resetField } = useFormContext(); const theme = useTheme(); const translate = useTranslate(); const initialDefaultValue = useRef(template || {}); const translateLabel = useTranslateLabel(); const removeField = useCallback( (index: number) => { remove(index); }, [remove] ); if (fields.length > 0) { const { ...rest } = fields[0]; initialDefaultValue.current = rest; // @ts-ignore for (const k in initialDefaultValue.current) initialDefaultValue.current[k] = null; } const addField = useCallback( (item: any = undefined) => { let defaultValue = item; if (item == null) { defaultValue = initialDefaultValue.current; if ( Children.count(children) === 1 && React.isValidElement(Children.only(children)) && // @ts-ignore !Children.only(children).props.source && // Make sure it's not a FormDataConsumer // @ts-ignore Children.map(children, (input) => React.isValidElement(input) && input.type !== FormDataConsumer).some( Boolean ) ) { // ArrayInput used for an array of scalar values // (e.g. tags: ['foo', 'bar']) defaultValue = ''; } else { // ArrayInput used for an array of objects // (e.g. authors: [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Doe' }]) defaultValue = defaultValue || ({} as Record); Children.forEach(children, (input) => { if (React.isValidElement(input) && input.type !== FormDataConsumer && input.props.source) { defaultValue[input.props.source] = input.props.defaultValue ?? null; } }); } } const newField = { ...defaultValue, ...template }; append(newField); // Make sure the newly added inputs are not considered dirty by react-hook-form resetField(`${source}.${fields.length}`, { defaultValue }); }, [append, children, resetField, source, fields.length, template] ); // add field and call the onClick event of the button passed as addButton prop function handleAddButtonClick(originalOnClickHandler: any) { return (event: MouseEvent) => { addField(); if (originalOnClickHandler) { originalOnClickHandler(event); } }; } const handleArrayClear = useCallback(() => { replace([]); }, [replace]); const context = useMemo( () => ({ total: fields.length, remove: removeField, source }), [fields.length, removeField, source] ); // @ts-ignore const tableBorderColor = theme.palette.mode === 'dark' ? theme.palette.grey.A400 : theme.palette.grey.A800; const showClearAllButton = fields.length > 0 && enableClearAll; const basicStyles = inset === true ? { borderRadius: 0, borderBottom: `1px solid ${theme.palette.divider}` } : { mt: 2, border: `1px solid ${tableBorderColor}`, //@ts-ignore boxShadow: theme.customShadows.z0 }; return fields ? ( // @ts-ignore
{React.cloneElement(addButton, { label, source, disableAdd, template, onClick: addButton?.type !== TableCreateButton ? handleAddButtonClick((props?.addButton as any)?.props?.onClick) : undefined, inset })} {/** @ts-ignore */} {Children.map(children, (input: ReactElement, index) => { if (input.type === EditButton) { return null; } if (!React.isValidElement(input)) { return null; } const columnText = ( {translateLabel({ ...input.props, resource })} ); return ( {input.props.title ? ( // @ts-ignore {columnText} ) : ( columnText )} ); })} {!disableRemove && showClearAllButton ? ( ) : ( )} {fields.length === 0 && ( React.isValidElement(child) && child.type === EditButton ).length + (disableRemove ? 0 : 1) } align="center" > {empty ? ( empty ) : ( - )} )} {fields.length > 0 && fields.map((member: any, index: number) => ( {children} ))}
) : null; } const TableFormIterator = styled(RawTableFormIterator, { slot: 'Root' })(({ theme }) => ({ '& > div.MuiPaper-root': { overflowX: 'auto', // this decision has been made with Roberto in order to avoid Marco gets angry [theme.breakpoints.down('sm')]: { width: `calc(100vw - ${theme.spacing(8.25)})` } }, [theme.breakpoints.down('sm')]: { '& .MuiTableRow-root:last-child': { borderBottom: 0 } } })); interface TableFormIteratorProps extends Partial { addButton?: ReactElement; children?: ReactNode; className?: string; disableAdd?: boolean; disableRemove?: boolean; enableClearAll?: boolean; /** * Adding the template prop will allow to set a default value for the new row. * * @example * * * * * * * * * * * */ template?: object; record?: RaRecord; label?: string | boolean; resource?: string; source?: string; sx?: SxProps; empty: ReactNode | string; inset?: boolean; } type ITableForm = typeof TableFormIterator & { /** * TableFormiterator allows to insert an addButton which will render the default btn and open a dialog with the form fields passed as children. * * @example * * * * * * * } * label="Property catalog"> * * * * * * * */ CreateButton: typeof TableCreateButton; /** * TableFormiterator allows a child EditButton which will render an edit button in the ActionsMenu of each row. * The EditButton will open a dialog with the form fields passed as children. * * @example * * * * * * * * * * * * * * * * * */ EditButton: typeof EditButton; /** * TableFormiterator allows a child Field which will render a read-only field in the Table with a custom record context. * The record context will overwritthe by getValues() of useFormContext() hook. * * @example * * * * * * * * * * * * * * * * * {...} * * * * */ Field: typeof Field; }; const DefaultTableForm = TableFormIterator as ITableForm; DefaultTableForm.CreateButton = TableCreateButton; DefaultTableForm.EditButton = EditButton; DefaultTableForm.Field = Field; export { DefaultTableForm as TableFormIterator }; export type { TableFormIteratorProps };