From cd1f1dc9caf6655008b9011fd21246aeb0c6ae44 Mon Sep 17 00:00:00 2001 From: Wayne Date: Tue, 1 Oct 2024 22:57:03 +0900 Subject: [PATCH] Check join date when validating timesheet --- src/app/api/timesheets/utils.ts | 24 ++++++++++++++----- src/components/LeaveModal/LeaveCalendar.tsx | 18 +++++++++++--- src/components/LeaveModal/LeaveModal.tsx | 2 ++ .../TimeLeaveModal/TimeLeaveModal.tsx | 13 +++++++--- .../UserWorkspacePage/UserWorkspacePage.tsx | 5 ++++ .../UserWorkspaceWrapper.tsx | 1 + src/config/authConfig.ts | 4 ++++ 7 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/app/api/timesheets/utils.ts b/src/app/api/timesheets/utils.ts index 7d1c856..82f06c9 100644 --- a/src/app/api/timesheets/utils.ts +++ b/src/app/api/timesheets/utils.ts @@ -4,6 +4,8 @@ import { LeaveEntry, RecordTimeLeaveInput, TimeEntry } from "./actions"; import { convertDateArrayToString } from "@/app/utils/formatUtil"; import compact from "lodash/compact"; import dayjs, { Dayjs } from "dayjs"; +import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; +dayjs.extend(isSameOrAfter); export type TimeEntryError = { [field in keyof TimeEntry]?: string; @@ -84,6 +86,7 @@ export const validateTimeLeaveRecord = ( records: RecordTimeLeaveInput, companyHolidays: HolidaysResult[], isFullTime?: boolean, + joinDate?: Dayjs, ): { [date: string]: string } | undefined => { const errors: { [date: string]: string } = {}; @@ -122,6 +125,7 @@ export const validateTimeLeaveRecord = ( entries.filter((e) => e.type === "leaveEntry") as LeaveEntry[], isHoliday, isFullTime, + joinDate, ); if (totalHourError) { @@ -138,6 +142,7 @@ export const checkTotalHours = ( leaves: LeaveEntry[], isHoliday?: boolean, isFullTime?: boolean, + joinDate?: Dayjs, ): string | undefined => { const leaveHours = leaves.reduce((acc, entry) => acc + entry.inputHours, 0); @@ -149,19 +154,26 @@ export const checkTotalHours = ( return acc + (entry.otHours || 0); }, 0); - if (totalInputHours + leaveHours > DAILY_NORMAL_MAX_HOURS) { + const totalHours = totalInputHours + leaveHours; + + const isDayToCheckAfterJoinDate = + joinDate && joinDate.isValid() ? dayJsObj.isSameOrAfter(joinDate) : true; + + if (!isDayToCheckAfterJoinDate && totalHours > 0) { + return "Cannot input hours before join date."; + } + + if (totalHours > DAILY_NORMAL_MAX_HOURS) { return "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours or decrease the leave hours."; } else if ( isFullTime && + isDayToCheckAfterJoinDate && !isHoliday && !dayJsObj.isSame(dayjs(), "day") && - totalInputHours + leaveHours !== DAILY_NORMAL_MAX_HOURS + totalHours !== DAILY_NORMAL_MAX_HOURS ) { return "The daily normal hours (timesheet hours + leave hours) for full-time staffs should be {{DAILY_NORMAL_MAX_HOURS}}."; - } else if ( - totalInputHours + totalOtHours + leaveHours > - TIMESHEET_DAILY_MAX_HOURS - ) { + } else if (totalHours + totalOtHours > TIMESHEET_DAILY_MAX_HOURS) { return "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}"; } }; diff --git a/src/components/LeaveModal/LeaveCalendar.tsx b/src/components/LeaveModal/LeaveCalendar.tsx index 0aa6336..e0dcaeb 100644 --- a/src/components/LeaveModal/LeaveCalendar.tsx +++ b/src/components/LeaveModal/LeaveCalendar.tsx @@ -24,7 +24,7 @@ import { } from "@/app/api/timesheets/actions"; import { Props as LeaveEditModalProps } from "../LeaveTable/LeaveEditModal"; import LeaveEditModal from "../LeaveTable/LeaveEditModal"; -import dayjs from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import { checkTotalHours } from "@/app/api/timesheets/utils"; import unionBy from "lodash/unionBy"; @@ -35,6 +35,7 @@ export interface Props { leaveRecords: RecordLeaveInput; timesheetRecords: RecordTimesheetInput; isFullTime: boolean; + joinDate: Dayjs; } interface EventClickArg { @@ -55,8 +56,12 @@ const LeaveCalendar: React.FC = ({ timesheetRecords, leaveRecords, isFullTime, + joinDate, }) => { - const { t ,i18n: { language }} = useTranslation(["home", "common"]); + const { + t, + i18n: { language }, + } = useTranslation(["home", "common"]); const locale = language === "zh" ? "zh-tw" : "en"; const theme = useTheme(); @@ -236,11 +241,18 @@ const LeaveCalendar: React.FC = ({ leavesWithNewEntry, Boolean(isHoliday), isFullTime, + joinDate, ); if (totalHourError) throw Error(totalHourError); }, - [companyHolidays, isFullTime, localLeaveRecords, timesheetRecords], + [ + companyHolidays, + isFullTime, + joinDate, + localLeaveRecords, + timesheetRecords, + ], ); const handleSaveLeave = useCallback( diff --git a/src/components/LeaveModal/LeaveModal.tsx b/src/components/LeaveModal/LeaveModal.tsx index 3739bd7..0bd5687 100644 --- a/src/components/LeaveModal/LeaveModal.tsx +++ b/src/components/LeaveModal/LeaveModal.tsx @@ -36,6 +36,7 @@ const LeaveModal: React.FC = ({ leaveRecords, timesheetRecords, isFullTime, + joinDate, }) => { const { t } = useTranslation("home"); const isMobile = useIsMobile(); @@ -43,6 +44,7 @@ const LeaveModal: React.FC = ({ const title = t("Record leave"); const content = ( = ({ fastEntryEnabled, leaveTypes, isFullTime, + joinDate, miscTasks, }) => { const { t } = useTranslation("home"); @@ -113,7 +115,12 @@ const TimeLeaveModal: React.FC = ({ const onSubmit = useCallback>( async (data) => { - const errors = validateTimeLeaveRecord(data, companyHolidays, isFullTime); + const errors = validateTimeLeaveRecord( + data, + companyHolidays, + isFullTime, + joinDate, + ); if (errors) { Object.keys(errors).forEach((date) => formProps.setError(date, { @@ -138,7 +145,7 @@ const TimeLeaveModal: React.FC = ({ formProps.reset(newFormValues); onClose(); }, - [companyHolidays, formProps, onClose, isFullTime], + [companyHolidays, isFullTime, joinDate, formProps, onClose], ); const onCancel = useCallback(() => { diff --git a/src/components/UserWorkspacePage/UserWorkspacePage.tsx b/src/components/UserWorkspacePage/UserWorkspacePage.tsx index b9deb2c..80a233b 100644 --- a/src/components/UserWorkspacePage/UserWorkspacePage.tsx +++ b/src/components/UserWorkspacePage/UserWorkspacePage.tsx @@ -26,6 +26,7 @@ import { TimesheetAmendmentModal } from "../TimesheetAmendment/TimesheetAmendmen import TimeLeaveModal from "../TimeLeaveModal/TimeLeaveModal"; import LeaveModal from "../LeaveModal"; import { Task } from "@/app/api/tasks"; +import dayjs from "dayjs"; export interface Props { leaveTypes: LeaveType[]; @@ -40,6 +41,7 @@ export interface Props { maintainNormalStaffWorkspaceAbility: boolean; maintainManagementStaffWorkspaceAbility: boolean; isFullTime: boolean; + joinDate?: number | null; miscTasks: Task[]; } @@ -61,6 +63,7 @@ const UserWorkspacePage: React.FC = ({ maintainNormalStaffWorkspaceAbility, maintainManagementStaffWorkspaceAbility, isFullTime, + joinDate, miscTasks, }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -192,6 +195,7 @@ const UserWorkspacePage: React.FC = ({ timesheetRecords={defaultTimesheets} leaveRecords={defaultLeaveRecords} isFullTime={isFullTime} + joinDate={dayjs(joinDate)} miscTasks={miscTasks} /> = ({ leaveRecords={defaultLeaveRecords} timesheetRecords={defaultTimesheets} isFullTime={isFullTime} + joinDate={dayjs(joinDate)} /> {assignedProjects.length > 0 ? ( { return (