Browse Source

Fix time leave input

tags/Baseline_30082024_FRONTEND_UAT
Wayne 1 year ago
parent
commit
757a828483
2 changed files with 142 additions and 69 deletions
  1. +141
    -68
      src/components/TimeLeaveModal/TimeLeaveInputTable.tsx
  2. +1
    -1
      src/components/TimesheetTable/TaskGroupSelect.tsx

+ 141
- 68
src/components/TimeLeaveModal/TimeLeaveInputTable.tsx View File

@@ -6,7 +6,6 @@ import {
GridCellParams, GridCellParams,
GridColDef, GridColDef,
GridEditInputCell, GridEditInputCell,
GridEventListener,
GridRenderEditCellParams, GridRenderEditCellParams,
GridRowId, GridRowId,
GridRowModel, GridRowModel,
@@ -67,6 +66,22 @@ export type TimeLeaveRow = Partial<
} }
>; >;


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

Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
}
}

const TimeLeaveInputTable: React.FC<Props> = ({ const TimeLeaveInputTable: React.FC<Props> = ({
day, day,
allProjects, allProjects,
@@ -133,38 +148,32 @@ const TimeLeaveInputTable: React.FC<Props> = ({
}, []); }, []);


const validateRow = useCallback( const validateRow = useCallback(
(id: GridRowId) => {
const row = apiRef.current.getRowWithUpdatedValues(
id,
"",
) as TimeLeaveRow;

// Test for warnings
(row: TimeLeaveRow) => {
if (row.type === "timeEntry") { if (row.type === "timeEntry") {
const error = validateTimeEntry(row, isHoliday);
let _isPlanned;
if (
row.projectId &&
row.taskGroupId &&
milestonesByProject[row.projectId]
) {
const milestone =
milestonesByProject[row.projectId][row.taskGroupId] || {};
const { startDate, endDate } = milestone;
// Check if the current day is between the start and end date inclusively
_isPlanned = dayjs(day).isBetween(startDate, endDate, "day", "[]");
}
apiRef.current.updateRows([{ id, _error: error, _isPlanned }]);
return !error;
} else if (row.type === "leaveEntry") {
const error = validateLeaveEntry(row, isHoliday);
apiRef.current.updateRows([{ id, _error: error }]);
return !error;
return validateTimeEntry(row, isHoliday);
} else { } else {
return false;
return validateLeaveEntry(row, isHoliday);
} }
}, },
[apiRef, day, isHoliday, milestonesByProject],
[isHoliday],
);

const verifyIsPlanned = useCallback(
(row: TimeLeaveRow) => {
if (
row.type === "timeEntry" &&
row.projectId &&
row.taskGroupId &&
milestonesByProject[row.projectId]
) {
const milestone =
milestonesByProject[row.projectId][row.taskGroupId] || {};
const { startDate, endDate } = milestone;
// Check if the current day is between the start and end date inclusively
return dayjs(day).isBetween(startDate, endDate, "day", "[]");
}
},
[day, milestonesByProject],
); );


const handleCancel = useCallback( const handleCancel = useCallback(
@@ -176,6 +185,14 @@ 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) => e.id !== id));
} else {
setEntries((es) =>
es.map((e) =>
e.id === id
? { ...e, _error: undefined, _isPlanned: undefined }
: e,
),
);
} }
}, },
[entries], [entries],
@@ -190,30 +207,60 @@ const TimeLeaveInputTable: React.FC<Props> = ({


const handleSave = useCallback( const handleSave = useCallback(
(id: GridRowId) => () => { (id: GridRowId) => () => {
if (validateRow(id)) {
setRowModesModel((model) => ({
...model,
[id]: { mode: GridRowModes.View },
}));
}
setRowModesModel((model) => ({
...model,
[id]: { mode: GridRowModes.View },
}));
}, },
[validateRow],
[],
); );


const handleEditStop = useCallback<GridEventListener<"rowEditStop">>(
(params, event) => {
if (!validateRow(params.id)) {
event.defaultMuiPrevented = true;
const processRowUpdate = useCallback(
(newRow: GridRowModel<TimeLeaveRow>) => {
const errors = validateRow(newRow);
if (errors) {
throw new ProcessRowUpdateError(newRow.id!, "validation error", errors);
} }

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { _isNew, _error, _isPlanned, ...updatedRow } = newRow;
const newIsPlanned = verifyIsPlanned(updatedRow);

const rowToSave = {
...updatedRow,
...(updatedRow.type === "timeEntry"
? {
leaveTypeId: undefined,
}
: {
projectId: undefined,
taskGroupId: undefined,
taskId: undefined,
}),
_isPlanned: newIsPlanned,
} satisfies TimeLeaveRow;
setEntries((es) =>
es.map((e) => (e.id === rowToSave.id ? rowToSave : e)),
);
return rowToSave;
}, },
[validateRow],
[validateRow, verifyIsPlanned],
); );


const processRowUpdate = useCallback((newRow: GridRowModel) => {
const updatedRow = { ...newRow, _isNew: false };
setEntries((es) => es.map((e) => (e.id === newRow.id ? updatedRow : e)));
return updatedRow;
}, []);
const onProcessRowUpdateError = useCallback(
(updateError: ProcessRowUpdateError) => {
const errors = updateError.errors;
const rowId = updateError.rowId;

apiRef.current.updateRows([
{
id: rowId,
_error: errors,
},
]);
},
[apiRef],
);


const columns = useMemo<GridColDef[]>( const columns = useMemo<GridColDef[]>(
() => [ () => [
@@ -249,6 +296,14 @@ const TimeLeaveInputTable: React.FC<Props> = ({
]; ];
}, },
}, },
{
field: "projectId",
editable: true,
},
{
field: "leaveTypeId",
editable: true,
},
{ {
field: "type", field: "type",
headerName: t("Project or Leave"), headerName: t("Project or Leave"),
@@ -293,23 +348,30 @@ const TimeLeaveInputTable: React.FC<Props> = ({
value: isLeave ? "leaveEntry" : "timeEntry", value: isLeave ? "leaveEntry" : "timeEntry",
}); });


params.api.updateRows([
{
id: params.id,
...(isLeave
? {
type: "leaveEntry",
leaveTypeId: projectOrLeaveId,
projectId: undefined,
}
: {
type: "timeEntry",
projectId: projectOrLeaveId,
leaveTypeId: undefined,
}),
_error: undefined,
},
]);
await params.api.setEditCellValue({
id: params.id,
field: isLeave ? "leaveTypeId" : "projectId",
value: projectOrLeaveId,
});

await params.api.setEditCellValue({
id: params.id,
field: isLeave ? "projectId" : "leaveTypeId",
value: undefined,
});

await params.api.setEditCellValue({
id: params.id,
field: "taskGroupId",
value: undefined,
});

await params.api.setEditCellValue({
id: params.id,
field: "taskId",
value: undefined,
});

params.api.setCellFocus( params.api.setCellFocus(
params.id, params.id,
isLeave || !projectOrLeaveId ? "inputHours" : "taskGroupId", isLeave || !projectOrLeaveId ? "inputHours" : "taskGroupId",
@@ -332,12 +394,17 @@ const TimeLeaveInputTable: React.FC<Props> = ({
projectId={params.row.projectId} projectId={params.row.projectId}
value={params.value} value={params.value}
taskGroupsByProject={taskGroupsByProject} taskGroupsByProject={taskGroupsByProject}
onTaskGroupSelect={(taskGroupId) => {
params.api.setEditCellValue({
onTaskGroupSelect={async (taskGroupId) => {
await params.api.setEditCellValue({
id: params.id, id: params.id,
field: params.field, field: params.field,
value: taskGroupId, value: taskGroupId,
}); });
await params.api.setEditCellValue({
id: params.id,
field: "taskId",
value: undefined,
});
params.api.setCellFocus(params.id, "taskId"); params.api.setCellFocus(params.id, "taskId");
}} }}
/> />
@@ -346,12 +413,17 @@ const TimeLeaveInputTable: React.FC<Props> = ({
return ( return (
<TaskGroupSelectWithoutProject <TaskGroupSelectWithoutProject
value={params.value} value={params.value}
onTaskGroupSelect={(taskGroupId) => {
params.api.setEditCellValue({
onTaskGroupSelect={async (taskGroupId) => {
await params.api.setEditCellValue({
id: params.id, id: params.id,
field: params.field, field: params.field,
value: taskGroupId, value: taskGroupId,
}); });
await params.api.setEditCellValue({
id: params.id,
field: "taskId",
value: undefined,
});
params.api.setCellFocus(params.id, "taskId"); params.api.setCellFocus(params.id, "taskId");
}} }}
taskGroups={taskGroupsWithoutProject} taskGroups={taskGroupsWithoutProject}
@@ -614,11 +686,12 @@ const TimeLeaveInputTable: React.FC<Props> = ({
}} }}
disableColumnMenu disableColumnMenu
editMode="row" editMode="row"
columnVisibilityModel={{ projectId: false, leaveTypeId: false }}
rows={entries} rows={entries}
rowModesModel={rowModesModel} rowModesModel={rowModesModel}
onRowModesModelChange={setRowModesModel} onRowModesModelChange={setRowModesModel}
onRowEditStop={handleEditStop}
processRowUpdate={processRowUpdate} processRowUpdate={processRowUpdate}
onProcessRowUpdateError={onProcessRowUpdateError}
columns={columns} columns={columns}
getCellClassName={(params: GridCellParams<TimeLeaveRow>) => { getCellClassName={(params: GridCellParams<TimeLeaveRow>) => {
let classname = ""; let classname = "";


+ 1
- 1
src/components/TimesheetTable/TaskGroupSelect.tsx View File

@@ -12,7 +12,7 @@ interface Props {
}; };
projectId: number | undefined; projectId: number | undefined;
value: number | undefined; value: number | undefined;
onTaskGroupSelect: (taskGroupId: number | string) => void;
onTaskGroupSelect: (taskGroupId: number | string) => Promise<void> | void;
error?: boolean; error?: boolean;
} }




Loading…
Cancel
Save