Browse Source

Use dynamic rowId to prevent timesheet/leave id clash

tags/Baseline_30082024_FRONTEND_UAT
Wayne 1 year ago
parent
commit
7ca124444c
1 changed files with 36 additions and 23 deletions
  1. +36
    -23
      src/components/TimeLeaveModal/TimeLeaveInputTable.tsx

+ 36
- 23
src/components/TimeLeaveModal/TimeLeaveInputTable.tsx View File

@@ -9,6 +9,7 @@ import {
GridRenderCellParams, GridRenderCellParams,
GridRenderEditCellParams, GridRenderEditCellParams,
GridRowId, GridRowId,
GridRowIdGetter,
GridRowModel, GridRowModel,
GridRowModes, GridRowModes,
GridRowModesModel, GridRowModesModel,
@@ -69,15 +70,15 @@ export type TimeLeaveRow = Partial<
>; >;


class ProcessRowUpdateError extends Error { class ProcessRowUpdateError extends Error {
public readonly rowId: GridRowId;
public readonly row: TimeLeaveRow;
public readonly errors: TimeEntryError | LeaveEntryError | undefined; public readonly errors: TimeEntryError | LeaveEntryError | undefined;
constructor( constructor(
rowId: GridRowId,
row: TimeLeaveRow,
message?: string, message?: string,
errors?: TimeEntryError | LeaveEntryError, errors?: TimeEntryError | LeaveEntryError,
) { ) {
super(message); super(message);
this.rowId = rowId;
this.row = row;
this.errors = errors; this.errors = errors;


Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
@@ -135,19 +136,28 @@ const TimeLeaveInputTable: React.FC<Props> = ({
useFormContext<RecordTimeLeaveInput>(); useFormContext<RecordTimeLeaveInput>();
const currentEntries = getValues(day); const currentEntries = getValues(day);


const getRowId = useCallback<GridRowIdGetter<TimeLeaveRow>>(
(row) => `${row.type}-${row.id}`,
[],
);

const [entries, setEntries] = useState<TimeLeaveRow[]>(currentEntries || []); const [entries, setEntries] = useState<TimeLeaveRow[]>(currentEntries || []);


const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});


const apiRef = useGridApiRef(); const apiRef = useGridApiRef();
const addRow = useCallback(() => { 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) => ({ setRowModesModel((model) => ({
...model, ...model,
[id]: { mode: GridRowModes.Edit, fieldToFocus: "projectId" },
[getRowId(newEntry)]: {
mode: GridRowModes.Edit,
fieldToFocus: "projectId",
},
})); }));
}, []);
}, [getRowId]);


const validateRow = useCallback( const validateRow = useCallback(
(row: TimeLeaveRow) => { (row: TimeLeaveRow) => {
@@ -186,25 +196,25 @@ const TimeLeaveInputTable: React.FC<Props> = ({
})); }));
const editedRow = entries.find((entry) => entry.id === id); const editedRow = entries.find((entry) => entry.id === id);
if (editedRow?._isNew) { if (editedRow?._isNew) {
setEntries((es) => es.filter((e) => e.id !== id));
setEntries((es) => es.filter((e) => getRowId(e) !== id));
} else { } else {
setEntries((es) => setEntries((es) =>
es.map((e) => es.map((e) =>
e.id === id
getRowId(e) === id
? { ...e, _error: undefined, _isPlanned: undefined } ? { ...e, _error: undefined, _isPlanned: undefined }
: e, : e,
), ),
); );
} }
}, },
[entries],
[entries, getRowId],
); );


const handleDelete = useCallback( const handleDelete = useCallback(
(id: GridRowId) => () => { (id: GridRowId) => () => {
setEntries((es) => es.filter((e) => e.id !== id));
setEntries((es) => es.filter((e) => getRowId(e) !== id));
}, },
[],
[getRowId],
); );


const handleSave = useCallback( const handleSave = useCallback(
@@ -218,10 +228,17 @@ const TimeLeaveInputTable: React.FC<Props> = ({
); );


const processRowUpdate = useCallback( const processRowUpdate = useCallback(
(newRow: GridRowModel<TimeLeaveRow>) => {
(
newRow: GridRowModel<TimeLeaveRow>,
originalRow: GridRowModel<TimeLeaveRow>,
) => {
const errors = validateRow(newRow); const errors = validateRow(newRow);
if (errors) { 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 // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -242,24 +259,19 @@ const TimeLeaveInputTable: React.FC<Props> = ({
_isPlanned: newIsPlanned, _isPlanned: newIsPlanned,
} satisfies TimeLeaveRow; } satisfies TimeLeaveRow;
setEntries((es) => setEntries((es) =>
es.map((e) => (e.id === rowToSave.id ? rowToSave : e)),
es.map((e) => (getRowId(e) === getRowId(originalRow) ? rowToSave : e)),
); );
return rowToSave; return rowToSave;
}, },
[validateRow, verifyIsPlanned],
[validateRow, verifyIsPlanned, getRowId],
); );


const onProcessRowUpdateError = useCallback( const onProcessRowUpdateError = useCallback(
(updateError: ProcessRowUpdateError) => { (updateError: ProcessRowUpdateError) => {
const errors = updateError.errors; 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], [apiRef],
); );
@@ -685,6 +697,7 @@ const TimeLeaveInputTable: React.FC<Props> = ({
return ( return (
<> <>
<StyledDataGrid <StyledDataGrid
getRowId={getRowId}
apiRef={apiRef} apiRef={apiRef}
autoHeight autoHeight
sx={{ sx={{


Loading…
Cancel
Save