From 61d7f400ef06191ce67b9f2e3863ecb5883fd635 Mon Sep 17 00:00:00 2001 From: Wayne Date: Tue, 7 May 2024 23:22:15 +0900 Subject: [PATCH] Update timesheet entry --- src/app/(main)/home/page.tsx | 6 +- src/app/api/projects/index.ts | 14 +- src/app/api/timesheets/actions.ts | 8 +- src/app/api/timesheets/utils.ts | 44 +++++ src/components/LeaveTable/LeaveEntryTable.tsx | 17 +- src/components/LeaveTable/LeaveTable.tsx | 14 +- .../TimesheetModal/TimesheetModal.tsx | 9 +- .../TimesheetTable/EntryInputTable.tsx | 187 ++++++++++-------- .../TimesheetTable/ProjectSelect.tsx | 89 +++++++++ .../TimesheetTable/TaskGroupSelect.tsx | 69 +++++++ src/components/TimesheetTable/TaskSelect.tsx | 72 +++++++ .../TimesheetTable/TimesheetTable.tsx | 29 ++- .../UserWorkspacePage/UserWorkspacePage.tsx | 5 +- .../UserWorkspaceWrapper.tsx | 20 +- 14 files changed, 464 insertions(+), 119 deletions(-) create mode 100644 src/app/api/timesheets/utils.ts create mode 100644 src/components/TimesheetTable/ProjectSelect.tsx create mode 100644 src/components/TimesheetTable/TaskGroupSelect.tsx create mode 100644 src/components/TimesheetTable/TaskSelect.tsx diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index 2766580..bd0c08a 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -8,7 +8,10 @@ import { } from "@/app/api/timesheets"; import { authOptions } from "@/config/authConfig"; import { getServerSession } from "next-auth"; -import { fetchAssignedProjects } from "@/app/api/projects"; +import { + fetchAssignedProjects, + fetchProjectWithTasks, +} from "@/app/api/projects"; export const metadata: Metadata = { title: "User Workspace", @@ -23,6 +26,7 @@ const Home: React.FC = async () => { fetchAssignedProjects(username); fetchLeaves(username); fetchLeaveTypes(); + fetchProjectWithTasks(); return ( diff --git a/src/app/api/projects/index.ts b/src/app/api/projects/index.ts index 9cc4f01..30bd385 100644 --- a/src/app/api/projects/index.ts +++ b/src/app/api/projects/index.ts @@ -49,7 +49,7 @@ export interface WorkNature { name: string; } -export interface AssignedProject { +export interface ProjectWithTasks { id: number; code: string; name: string; @@ -60,6 +60,9 @@ export interface AssignedProject { endDate?: string; }; }; +} + +export interface AssignedProject extends ProjectWithTasks { // Manhour info hoursSpent: number; hoursSpentOther: number; @@ -147,6 +150,15 @@ export const fetchAssignedProjects = cache(async (username: string) => { ); }); +export const fetchProjectWithTasks = cache(async () => { + return serverFetchJson( + `${BASE_API_URL}/projects/allProjectWithTasks`, + { + next: { tags: ["allProjectWithTasks"] }, + }, + ); +}); + export const fetchProjectDetails = cache(async (projectId: string) => { return serverFetchJson( `${BASE_API_URL}/projects/projectDetails/${projectId}`, diff --git a/src/app/api/timesheets/actions.ts b/src/app/api/timesheets/actions.ts index 97b03a9..c836e6e 100644 --- a/src/app/api/timesheets/actions.ts +++ b/src/app/api/timesheets/actions.ts @@ -8,10 +8,11 @@ import { revalidateTag } from "next/cache"; export interface TimeEntry { id: number; - projectId: ProjectResult["id"]; - taskGroupId: TaskGroup["id"]; - taskId: Task["id"]; + projectId?: ProjectResult["id"]; + taskGroupId?: TaskGroup["id"]; + taskId?: Task["id"]; inputHours: number; + remark?: string; } export interface RecordTimesheetInput { @@ -22,6 +23,7 @@ export interface LeaveEntry { id: number; inputHours: number; leaveTypeId: number; + remark?: string; } export interface RecordLeaveInput { diff --git a/src/app/api/timesheets/utils.ts b/src/app/api/timesheets/utils.ts new file mode 100644 index 0000000..68ee075 --- /dev/null +++ b/src/app/api/timesheets/utils.ts @@ -0,0 +1,44 @@ +import { LeaveEntry, TimeEntry } from "./actions"; + +/** + * @param entry - the time entry + * @returns the field where there is an error, or an empty string if there is none + */ +export const isValidTimeEntry = (entry: Partial): string => { + // Test for errors + let error: keyof TimeEntry | "" = ""; + + // If there is a project id, there should also be taskGroupId, taskId, inputHours + if (entry.projectId) { + if (!entry.taskGroupId) { + error = "taskGroupId"; + } else if (!entry.taskId) { + error = "taskId"; + } else if (!entry.inputHours || !(entry.inputHours >= 0)) { + error = "inputHours"; + } + } else { + if (!entry.inputHours || !(entry.inputHours >= 0)) { + error = "inputHours"; + } else if (!entry.remark) { + error = "remark"; + } + } + + return error; +}; + +export const isValidLeaveEntry = (entry: Partial): string => { + // Test for errrors + let error: keyof LeaveEntry | "" = ""; + if (!entry.leaveTypeId) { + error = "leaveTypeId"; + } else if (!entry.inputHours || !(entry.inputHours >= 0)) { + error = "inputHours"; + } + + return error; +}; + +export const LEAVE_DAILY_MAX_HOURS = 8; +export const TIMESHEET_DAILY_MAX_HOURS = 20; diff --git a/src/components/LeaveTable/LeaveEntryTable.tsx b/src/components/LeaveTable/LeaveEntryTable.tsx index 9e9170d..dd8fda7 100644 --- a/src/components/LeaveTable/LeaveEntryTable.tsx +++ b/src/components/LeaveTable/LeaveEntryTable.tsx @@ -21,6 +21,7 @@ import { manhourFormatter } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; import isBetween from "dayjs/plugin/isBetween"; import { LeaveType } from "@/app/api/timesheets"; +import { isValidLeaveEntry } from "@/app/api/timesheets/utils"; dayjs.extend(isBetween); @@ -63,13 +64,7 @@ const EntryInputTable: React.FC = ({ day, leaveTypes }) => { "", ) as LeaveEntryRow; - // Test for errrors - let error: keyof LeaveEntry | "" = ""; - if (!row.leaveTypeId) { - error = "leaveTypeId"; - } else if (!row.inputHours || !(row.inputHours >= 0)) { - error = "inputHours"; - } + const error = isValidLeaveEntry(row); apiRef.current.updateRows([{ id, _error: error }]); return !error; @@ -182,6 +177,13 @@ const EntryInputTable: React.FC = ({ day, leaveTypes }) => { return manhourFormatter.format(params.value); }, }, + { + field: "remark", + headerName: t("Remark"), + sortable: false, + flex: 1, + editable: true, + }, ], [t, rowModesModel, handleDelete, handleSave, handleCancel, leaveTypes], ); @@ -197,6 +199,7 @@ const EntryInputTable: React.FC = ({ day, leaveTypes }) => { id: e.id!, inputHours: e.inputHours!, leaveTypeId: e.leaveTypeId!, + remark: e.remark, })), ]); }, [getValues, entries, setValue, day]); diff --git a/src/components/LeaveTable/LeaveTable.tsx b/src/components/LeaveTable/LeaveTable.tsx index 5d0a003..12097c5 100644 --- a/src/components/LeaveTable/LeaveTable.tsx +++ b/src/components/LeaveTable/LeaveTable.tsx @@ -19,13 +19,12 @@ import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import LeaveEntryTable from "./LeaveEntryTable"; import { LeaveType } from "@/app/api/timesheets"; +import { LEAVE_DAILY_MAX_HOURS } from "@/app/api/timesheets/utils"; interface Props { leaveTypes: LeaveType[]; } -const MAX_HOURS = 8; - const LeaveTable: React.FC = ({ leaveTypes }) => { const { t } = useTranslation("home"); @@ -94,17 +93,22 @@ const DayRow: React.FC<{ {shortDateFormatter(language).format(dayJsObj.toDate())} MAX_HOURS ? "error.main" : undefined }} + sx={{ + color: + totalHours > LEAVE_DAILY_MAX_HOURS ? "error.main" : undefined, + }} > {manhourFormatter.format(totalHours)} - {totalHours > MAX_HOURS && ( + {totalHours > LEAVE_DAILY_MAX_HOURS && ( - {t("(the daily total hours cannot be more than 8.)")} + {t("(the daily total hours cannot be more than {{hours}})", { + hours: LEAVE_DAILY_MAX_HOURS, + })} )} diff --git a/src/components/TimesheetModal/TimesheetModal.tsx b/src/components/TimesheetModal/TimesheetModal.tsx index e8e5061..3d55878 100644 --- a/src/components/TimesheetModal/TimesheetModal.tsx +++ b/src/components/TimesheetModal/TimesheetModal.tsx @@ -19,11 +19,12 @@ import { } from "@/app/api/timesheets/actions"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; -import { AssignedProject } from "@/app/api/projects"; +import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; interface Props { isOpen: boolean; onClose: () => void; + allProjects: ProjectWithTasks[]; assignedProjects: AssignedProject[]; username: string; defaultTimesheets?: RecordTimesheetInput; @@ -42,6 +43,7 @@ const modalSx: SxProps = { const TimesheetModal: React.FC = ({ isOpen, onClose, + allProjects, assignedProjects, username, defaultTimesheets, @@ -106,7 +108,10 @@ const TimesheetModal: React.FC = ({ marginBlock: 4, }} > - +