From 7ca124444cbeefe4d437cf98c74d877c6e550f6c Mon Sep 17 00:00:00 2001 From: Wayne Date: Sun, 25 Aug 2024 22:27:55 +0900 Subject: [PATCH] Use dynamic rowId to prevent timesheet/leave id clash --- .../TimeLeaveModal/TimeLeaveInputTable.tsx | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/components/TimeLeaveModal/TimeLeaveInputTable.tsx b/src/components/TimeLeaveModal/TimeLeaveInputTable.tsx index 9e85bf2..980dccc 100644 --- a/src/components/TimeLeaveModal/TimeLeaveInputTable.tsx +++ b/src/components/TimeLeaveModal/TimeLeaveInputTable.tsx @@ -9,6 +9,7 @@ import { GridRenderCellParams, GridRenderEditCellParams, GridRowId, + GridRowIdGetter, GridRowModel, GridRowModes, GridRowModesModel, @@ -69,15 +70,15 @@ export type TimeLeaveRow = Partial< >; class ProcessRowUpdateError extends Error { - public readonly rowId: GridRowId; + public readonly row: TimeLeaveRow; public readonly errors: TimeEntryError | LeaveEntryError | undefined; constructor( - rowId: GridRowId, + row: TimeLeaveRow, message?: string, errors?: TimeEntryError | LeaveEntryError, ) { super(message); - this.rowId = rowId; + this.row = row; this.errors = errors; Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); @@ -135,19 +136,28 @@ const TimeLeaveInputTable: React.FC = ({ useFormContext(); const currentEntries = getValues(day); + const getRowId = useCallback>( + (row) => `${row.type}-${row.id}`, + [], + ); + const [entries, setEntries] = useState(currentEntries || []); const [rowModesModel, setRowModesModel] = useState({}); const apiRef = useGridApiRef(); const addRow = useCallback(() => { - const id = Date.now(); - setEntries((e) => [...e, { id, _isNew: true, type: "timeEntry" }]); + const type = "timeEntry" as const; + const newEntry = { id: Date.now(), _isNew: true, type }; + setEntries((e) => [...e, newEntry]); setRowModesModel((model) => ({ ...model, - [id]: { mode: GridRowModes.Edit, fieldToFocus: "projectId" }, + [getRowId(newEntry)]: { + mode: GridRowModes.Edit, + fieldToFocus: "projectId", + }, })); - }, []); + }, [getRowId]); const validateRow = useCallback( (row: TimeLeaveRow) => { @@ -186,25 +196,25 @@ const TimeLeaveInputTable: React.FC = ({ })); const editedRow = entries.find((entry) => entry.id === id); if (editedRow?._isNew) { - setEntries((es) => es.filter((e) => e.id !== id)); + setEntries((es) => es.filter((e) => getRowId(e) !== id)); } else { setEntries((es) => es.map((e) => - e.id === id + getRowId(e) === id ? { ...e, _error: undefined, _isPlanned: undefined } : e, ), ); } }, - [entries], + [entries, getRowId], ); const handleDelete = useCallback( (id: GridRowId) => () => { - setEntries((es) => es.filter((e) => e.id !== id)); + setEntries((es) => es.filter((e) => getRowId(e) !== id)); }, - [], + [getRowId], ); const handleSave = useCallback( @@ -218,10 +228,17 @@ const TimeLeaveInputTable: React.FC = ({ ); const processRowUpdate = useCallback( - (newRow: GridRowModel) => { + ( + newRow: GridRowModel, + originalRow: GridRowModel, + ) => { const errors = validateRow(newRow); if (errors) { - throw new ProcessRowUpdateError(newRow.id!, "validation error", errors); + throw new ProcessRowUpdateError( + originalRow, + "validation error", + errors, + ); } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -242,24 +259,19 @@ const TimeLeaveInputTable: React.FC = ({ _isPlanned: newIsPlanned, } satisfies TimeLeaveRow; setEntries((es) => - es.map((e) => (e.id === rowToSave.id ? rowToSave : e)), + es.map((e) => (getRowId(e) === getRowId(originalRow) ? rowToSave : e)), ); return rowToSave; }, - [validateRow, verifyIsPlanned], + [validateRow, verifyIsPlanned, getRowId], ); const onProcessRowUpdateError = useCallback( (updateError: ProcessRowUpdateError) => { const errors = updateError.errors; - const rowId = updateError.rowId; + const oldRow = updateError.row; - apiRef.current.updateRows([ - { - id: rowId, - _error: errors, - }, - ]); + apiRef.current.updateRows([{ ...oldRow, _error: errors }]); }, [apiRef], ); @@ -685,6 +697,7 @@ const TimeLeaveInputTable: React.FC = ({ return ( <>