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,
GridColDef,
GridEditInputCell,
GridEventListener,
GridRenderEditCellParams,
GridRowId,
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> = ({
day,
allProjects,
@@ -133,38 +148,32 @@ const TimeLeaveInputTable: React.FC<Props> = ({
}, []);

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

// Test for warnings
(row: TimeLeaveRow) => {
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 {
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(
@@ -176,6 +185,14 @@ const TimeLeaveInputTable: React.FC<Props> = ({
const editedRow = entries.find((entry) => entry.id === id);
if (editedRow?._isNew) {
setEntries((es) => es.filter((e) => e.id !== id));
} else {
setEntries((es) =>
es.map((e) =>
e.id === id
? { ...e, _error: undefined, _isPlanned: undefined }
: e,
),
);
}
},
[entries],
@@ -190,30 +207,60 @@ const TimeLeaveInputTable: React.FC<Props> = ({

const handleSave = useCallback(
(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[]>(
() => [
@@ -249,6 +296,14 @@ const TimeLeaveInputTable: React.FC<Props> = ({
];
},
},
{
field: "projectId",
editable: true,
},
{
field: "leaveTypeId",
editable: true,
},
{
field: "type",
headerName: t("Project or Leave"),
@@ -293,23 +348,30 @@ const TimeLeaveInputTable: React.FC<Props> = ({
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.id,
isLeave || !projectOrLeaveId ? "inputHours" : "taskGroupId",
@@ -332,12 +394,17 @@ const TimeLeaveInputTable: React.FC<Props> = ({
projectId={params.row.projectId}
value={params.value}
taskGroupsByProject={taskGroupsByProject}
onTaskGroupSelect={(taskGroupId) => {
params.api.setEditCellValue({
onTaskGroupSelect={async (taskGroupId) => {
await params.api.setEditCellValue({
id: params.id,
field: params.field,
value: taskGroupId,
});
await params.api.setEditCellValue({
id: params.id,
field: "taskId",
value: undefined,
});
params.api.setCellFocus(params.id, "taskId");
}}
/>
@@ -346,12 +413,17 @@ const TimeLeaveInputTable: React.FC<Props> = ({
return (
<TaskGroupSelectWithoutProject
value={params.value}
onTaskGroupSelect={(taskGroupId) => {
params.api.setEditCellValue({
onTaskGroupSelect={async (taskGroupId) => {
await params.api.setEditCellValue({
id: params.id,
field: params.field,
value: taskGroupId,
});
await params.api.setEditCellValue({
id: params.id,
field: "taskId",
value: undefined,
});
params.api.setCellFocus(params.id, "taskId");
}}
taskGroups={taskGroupsWithoutProject}
@@ -614,11 +686,12 @@ const TimeLeaveInputTable: React.FC<Props> = ({
}}
disableColumnMenu
editMode="row"
columnVisibilityModel={{ projectId: false, leaveTypeId: false }}
rows={entries}
rowModesModel={rowModesModel}
onRowModesModelChange={setRowModesModel}
onRowEditStop={handleEditStop}
processRowUpdate={processRowUpdate}
onProcessRowUpdateError={onProcessRowUpdateError}
columns={columns}
getCellClassName={(params: GridCellParams<TimeLeaveRow>) => {
let classname = "";


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

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



Loading…
Cancel
Save