diff --git a/src/components/DateHoursTable/DateHoursList.tsx b/src/components/DateHoursTable/DateHoursList.tsx new file mode 100644 index 0000000..75b991b --- /dev/null +++ b/src/components/DateHoursTable/DateHoursList.tsx @@ -0,0 +1,205 @@ +import { + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; +import { ArrowBack, Check } from "@mui/icons-material"; +import { + Box, + Button, + Card, + CardActionArea, + CardContent, + Stack, + Typography, +} from "@mui/material"; +import dayjs from "dayjs"; +import React, { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + LEAVE_DAILY_MAX_HOURS, + TIMESHEET_DAILY_MAX_HOURS, +} from "@/app/api/timesheets/utils"; + +interface Props { + days: string[]; + leaveEntries: RecordLeaveInput; + timesheetEntries: RecordTimesheetInput; + EntryComponent: React.FunctionComponent< + EntryComponentProps & { date: string } + >; + entryComponentProps: EntryComponentProps; +} + +function DateHoursList({ + days, + leaveEntries, + timesheetEntries, + EntryComponent, + entryComponentProps, +}: Props) { + const { + t, + i18n: { language }, + } = useTranslation("home"); + + const [selectedDate, setSelectedDate] = useState(""); + const isDateSelected = selectedDate !== ""; + + const makeSelectDate = useCallback( + (date: string) => () => { + setSelectedDate(date); + }, + [], + ); + + const onDateDone = useCallback>( + (e) => { + setSelectedDate(""); + e.preventDefault(); + }, + [], + ); + + return ( + <> + {isDateSelected ? ( + + ) : ( + + {days.map((day, index) => { + const dayJsObj = dayjs(day); + const leaves = leaveEntries[day]; + const leaveHours = + leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; + + const timesheet = timesheetEntries[day]; + const timesheetHours = + timesheet?.reduce( + (acc, entry) => + acc + (entry.inputHours || 0) + (entry.otHours || 0), + 0, + ) || 0; + + const dailyTotal = leaveHours + timesheetHours; + + const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; + const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; + + return ( + + + + + {shortDateFormatter(language).format(dayJsObj.toDate())} + + + + + {t("Timesheet Hours")} + + + {manhourFormatter.format(timesheetHours)} + + + + + {t("Leave Hours")} + + + {manhourFormatter.format(leaveHours)} + + {leaveExceeded && ( + + {t("Leave hours cannot be more than {{hours}}", { + hours: LEAVE_DAILY_MAX_HOURS, + })} + + )} + + + + + {t("Daily Total Hours")} + + + {manhourFormatter.format(timesheetHours + leaveHours)} + + {dailyTotalExceeded && ( + + {t( + "The daily total hours cannot be more than {{hours}}", + { + hours: TIMESHEET_DAILY_MAX_HOURS, + }, + )} + + )} + + + + + + ); + })} + + )} + + {isDateSelected ? ( + + ) : ( + + )} + + + ); +} + +export default DateHoursList; diff --git a/src/components/DateHoursTable/DateHoursTable.tsx b/src/components/DateHoursTable/DateHoursTable.tsx new file mode 100644 index 0000000..cd897b1 --- /dev/null +++ b/src/components/DateHoursTable/DateHoursTable.tsx @@ -0,0 +1,196 @@ +import { + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; +import { Info, KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material"; +import { + Box, + Collapse, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tooltip, +} from "@mui/material"; +import dayjs from "dayjs"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + LEAVE_DAILY_MAX_HOURS, + TIMESHEET_DAILY_MAX_HOURS, +} from "@/app/api/timesheets/utils"; + +interface Props { + days: string[]; + leaveEntries: RecordLeaveInput; + timesheetEntries: RecordTimesheetInput; + EntryTableComponent: React.FunctionComponent< + EntryTableProps & { day: string } + >; + entryTableProps: EntryTableProps; +} + +function DateHoursTable({ + days, + EntryTableComponent, + entryTableProps, + leaveEntries, + timesheetEntries, +}: Props) { + const { t } = useTranslation("home"); + + return ( + + + + + + {t("Date")} + {t("Timesheet Hours")} + {t("Leave Hours")} + {t("Daily Total Hours")} + + + + {days.map((day, index) => { + return ( + + ); + })} + +
+
+ ); +} + +function DayRow({ + day, + leaveEntries, + timesheetEntries, + entryTableProps, + EntryTableComponent, +}: { + day: string; + leaveEntries: RecordLeaveInput; + timesheetEntries: RecordTimesheetInput; + EntryTableComponent: React.FunctionComponent< + EntryTableProps & { day: string } + >; + entryTableProps: EntryTableProps; +}) { + const { + t, + i18n: { language }, + } = useTranslation("home"); + const dayJsObj = dayjs(day); + const [open, setOpen] = useState(false); + + const leaves = leaveEntries[day]; + const leaveHours = + leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; + + const timesheet = timesheetEntries[day]; + const timesheetHours = + timesheet?.reduce( + (acc, entry) => acc + (entry.inputHours || 0) + (entry.otHours || 0), + 0, + ) || 0; + + const dailyTotal = leaveHours + timesheetHours; + + const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; + const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; + + return ( + <> + + + setOpen(!open)} + > + {open ? : } + + + + {shortDateFormatter(language).format(dayJsObj.toDate())} + + {/* Timesheet */} + {manhourFormatter.format(timesheetHours)} + {/* Leave total */} + + + {manhourFormatter.format(leaveHours)} + {leaveExceeded && ( + + + + )} + + + {/* Daily total */} + + + {manhourFormatter.format(dailyTotal)} + {dailyTotalExceeded && ( + + + + )} + + + + + + + {} + + + + + ); +} + +export default DateHoursTable; diff --git a/src/components/DateHoursTable/index.ts b/src/components/DateHoursTable/index.ts new file mode 100644 index 0000000..9acba4b --- /dev/null +++ b/src/components/DateHoursTable/index.ts @@ -0,0 +1 @@ +export { default } from "./DateHoursTable"; diff --git a/src/components/FullscreenModal/FullscreenModal.tsx b/src/components/FullscreenModal/FullscreenModal.tsx new file mode 100644 index 0000000..11aa36b --- /dev/null +++ b/src/components/FullscreenModal/FullscreenModal.tsx @@ -0,0 +1,46 @@ +import { Close } from "@mui/icons-material"; +import { + Box, + IconButton, + Modal, + ModalProps, + Paper, + Slide, +} from "@mui/material"; + +interface Props extends ModalProps { + closeModal: () => void; +} + +const FullscreenModal: React.FC = ({ + children, + closeModal, + ...props +}) => { + return ( + + + + + + + + + + {children} + + + + + ); +}; + +export default FullscreenModal; diff --git a/src/components/FullscreenModal/index.ts b/src/components/FullscreenModal/index.ts new file mode 100644 index 0000000..5cc4ad7 --- /dev/null +++ b/src/components/FullscreenModal/index.ts @@ -0,0 +1 @@ +export { default } from "./FullscreenModal"; diff --git a/src/components/LeaveModal/LeaveModal.tsx b/src/components/LeaveModal/LeaveModal.tsx index c55c1c0..a6a551c 100644 --- a/src/components/LeaveModal/LeaveModal.tsx +++ b/src/components/LeaveModal/LeaveModal.tsx @@ -9,15 +9,23 @@ import { ModalProps, SxProps, Typography, + useMediaQuery, + useTheme, } from "@mui/material"; import { useTranslation } from "react-i18next"; import { Check, Close } from "@mui/icons-material"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; -import { RecordLeaveInput, saveLeave } from "@/app/api/timesheets/actions"; +import { + RecordLeaveInput, + RecordTimesheetInput, + saveLeave, +} from "@/app/api/timesheets/actions"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import LeaveTable from "../LeaveTable"; import { LeaveType } from "@/app/api/timesheets"; +import FullscreenModal from "../FullscreenModal"; +import MobileLeaveTable from "../LeaveTable/MobileLeaveTable"; interface Props { isOpen: boolean; @@ -25,6 +33,7 @@ interface Props { username: string; defaultLeaveRecords?: RecordLeaveInput; leaveTypes: LeaveType[]; + timesheetRecords: RecordTimesheetInput; } const modalSx: SxProps = { @@ -34,7 +43,7 @@ const modalSx: SxProps = { transform: "translate(-50%, -50%)", width: { xs: "calc(100% - 2rem)", sm: "90%" }, maxHeight: "90%", - maxWidth: 1200, + maxWidth: 1400, }; const LeaveModal: React.FC = ({ @@ -42,6 +51,7 @@ const LeaveModal: React.FC = ({ onClose, username, defaultLeaveRecords, + timesheetRecords, leaveTypes, }) => { const { t } = useTranslation("home"); @@ -90,47 +100,80 @@ const LeaveModal: React.FC = ({ const onModalClose = useCallback>( (_, reason) => { if (reason !== "backdropClick") { - onClose(); + onCancel(); } }, - [onClose], + [onCancel], ); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.up("sm")); + return ( - - - - + {matches ? ( + // Desktop version + + + + + {t("Record Leave")} + + + + + + + + + + + + ) : ( + // Mobile version + + - + {t("Record Leave")} - - - - - - - - - - - + + + + )} + ); }; diff --git a/src/components/LeaveTable/LeaveEditModal.tsx b/src/components/LeaveTable/LeaveEditModal.tsx new file mode 100644 index 0000000..c176930 --- /dev/null +++ b/src/components/LeaveTable/LeaveEditModal.tsx @@ -0,0 +1,132 @@ +import { LeaveType } from "@/app/api/timesheets"; +import { LeaveEntry } from "@/app/api/timesheets/actions"; +import { Check, Delete } from "@mui/icons-material"; +import { + Box, + Button, + FormControl, + InputLabel, + MenuItem, + Modal, + ModalProps, + Paper, + Select, + SxProps, + TextField, +} from "@mui/material"; +import React, { useCallback, useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +export interface Props extends Omit { + onSave: (leaveEntry: LeaveEntry) => void; + onDelete?: () => void; + leaveTypes: LeaveType[]; + defaultValues?: Partial; +} + +const modalSx: SxProps = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "90%", + maxHeight: "90%", + padding: 3, + display: "flex", + flexDirection: "column", + gap: 2, +}; +const LeaveEditModal: React.FC = ({ + onSave, + onDelete, + open, + onClose, + leaveTypes, + defaultValues, +}) => { + const { t } = useTranslation("home"); + const { register, control, reset, getValues, trigger, formState } = + useForm(); + + useEffect(() => { + reset(defaultValues ?? { leaveTypeId: leaveTypes[0].id, id: Date.now() }); + }, [defaultValues, leaveTypes, reset]); + + const saveHandler = useCallback(async () => { + const valid = await trigger(); + if (valid) { + onSave(getValues()); + } + }, [getValues, onSave, trigger]); + + const closeHandler = useCallback>( + (...args) => { + onClose?.(...args); + reset(); + }, + [onClose, reset], + ); + + return ( + + + + {t("Leave Type")} + ( + + )} + /> + + value > 0, + })} + error={Boolean(formState.errors.inputHours)} + /> + + + {onDelete && ( + + )} + + + + + ); +}; + +export default LeaveEditModal; diff --git a/src/components/LeaveTable/LeaveEntryTable.tsx b/src/components/LeaveTable/LeaveEntryTable.tsx index dd8fda7..9fb8172 100644 --- a/src/components/LeaveTable/LeaveEntryTable.tsx +++ b/src/components/LeaveTable/LeaveEntryTable.tsx @@ -169,8 +169,8 @@ const EntryInputTable: React.FC = ({ day, leaveTypes }) => { }, { field: "inputHours", - headerName: t("Hours"), - width: 100, + headerName: t("Leave Hours"), + width: 150, editable: true, type: "number", valueFormatter(params) { diff --git a/src/components/LeaveTable/LeaveTable.tsx b/src/components/LeaveTable/LeaveTable.tsx index 12097c5..ce71d12 100644 --- a/src/components/LeaveTable/LeaveTable.tsx +++ b/src/components/LeaveTable/LeaveTable.tsx @@ -1,136 +1,31 @@ -import { RecordLeaveInput, LeaveEntry } from "@/app/api/timesheets/actions"; -import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; -import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material"; import { - Box, - Collapse, - IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; -import dayjs from "dayjs"; -import React, { useState } from "react"; + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import React from "react"; 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"; +import DateHoursTable from "../DateHoursTable"; interface Props { leaveTypes: LeaveType[]; + timesheetRecords: RecordTimesheetInput; } -const LeaveTable: React.FC = ({ leaveTypes }) => { - const { t } = useTranslation("home"); - +const LeaveTable: React.FC = ({ leaveTypes, timesheetRecords }) => { const { watch } = useFormContext(); const currentInput = watch(); const days = Object.keys(currentInput); return ( - - - - - - {t("Date")} - {t("Daily Total Hours")} - - - - {days.map((day, index) => { - const entries = currentInput[day]; - return ( - - ); - })} - -
-
- ); -}; - -const DayRow: React.FC<{ - day: string; - entries: LeaveEntry[]; - leaveTypes: LeaveType[]; -}> = ({ day, entries, leaveTypes }) => { - const { - t, - i18n: { language }, - } = useTranslation("home"); - const dayJsObj = dayjs(day); - const [open, setOpen] = useState(false); - - const totalHours = entries.reduce((acc, entry) => acc + entry.inputHours, 0); - - return ( - <> - - - setOpen(!open)} - > - {open ? : } - - - - {shortDateFormatter(language).format(dayJsObj.toDate())} - - LEAVE_DAILY_MAX_HOURS ? "error.main" : undefined, - }} - > - {manhourFormatter.format(totalHours)} - {totalHours > LEAVE_DAILY_MAX_HOURS && ( - - {t("(the daily total hours cannot be more than {{hours}})", { - hours: LEAVE_DAILY_MAX_HOURS, - })} - - )} - - - - - - - - - - - - + ); }; diff --git a/src/components/LeaveTable/MobileLeaveEntry.tsx b/src/components/LeaveTable/MobileLeaveEntry.tsx new file mode 100644 index 0000000..90fd19a --- /dev/null +++ b/src/components/LeaveTable/MobileLeaveEntry.tsx @@ -0,0 +1,179 @@ +import { LeaveType } from "@/app/api/timesheets"; +import { LeaveEntry, RecordLeaveInput } from "@/app/api/timesheets/actions"; +import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; +import { Add, Edit } from "@mui/icons-material"; +import { + Box, + Button, + Card, + CardContent, + IconButton, + Typography, +} from "@mui/material"; +import dayjs from "dayjs"; +import React, { useCallback, useMemo, useState } from "react"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import LeaveEditModal, { Props as LeaveEditModalProps } from "./LeaveEditModal"; + +interface Props { + date: string; + leaveTypes: LeaveType[]; +} + +const MobileLeaveEntry: React.FC = ({ date, leaveTypes }) => { + const { + t, + i18n: { language }, + } = useTranslation("home"); + const dayJsObj = dayjs(date); + + const leaveTypeMap = useMemo<{ [id: LeaveType["id"]]: LeaveType }>(() => { + return leaveTypes.reduce( + (acc, leaveType) => ({ ...acc, [leaveType.id]: leaveType }), + {}, + ); + }, [leaveTypes]); + + const { watch, setValue } = useFormContext(); + const currentEntries = watch(date); + + // Edit modal + const [editModalProps, setEditModalProps] = useState< + Partial + >({}); + const [editModalOpen, setEditModalOpen] = useState(false); + + const openEditModal = useCallback( + (defaultValues?: LeaveEntry) => () => { + setEditModalProps({ + defaultValues, + onDelete: defaultValues + ? () => { + setValue( + date, + currentEntries.filter((entry) => entry.id !== defaultValues.id), + ); + setEditModalOpen(false); + } + : undefined, + }); + setEditModalOpen(true); + }, + [currentEntries, date, setValue], + ); + + const closeEditModal = useCallback(() => { + setEditModalOpen(false); + }, []); + + const onSaveEntry = useCallback( + (entry: LeaveEntry) => { + const existingEntry = currentEntries.find((e) => e.id === entry.id); + if (existingEntry) { + setValue( + date, + currentEntries.map((e) => ({ + ...(e.id === existingEntry.id ? entry : e), + })), + ); + } else { + setValue(date, [...currentEntries, entry]); + } + setEditModalOpen(false); + }, + [currentEntries, date, setValue], + ); + + return ( + + + {shortDateFormatter(language).format(dayJsObj.toDate())} + + {currentEntries.length ? ( + currentEntries.map((entry, index) => { + return ( + + + + + + {leaveTypeMap[entry.leaveTypeId].name} + + + {manhourFormatter.format(entry.inputHours)} + + + + + + + {entry.remark && ( + + + {t("Remark")} + + {entry.remark} + + )} + + + ); + }) + ) : ( + + {t("Add some leave entries!")} + + )} + + + + + + ); +}; + +export default MobileLeaveEntry; diff --git a/src/components/LeaveTable/MobileLeaveTable.tsx b/src/components/LeaveTable/MobileLeaveTable.tsx new file mode 100644 index 0000000..0bafd30 --- /dev/null +++ b/src/components/LeaveTable/MobileLeaveTable.tsx @@ -0,0 +1,35 @@ +import { + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import React from "react"; +import { useFormContext } from "react-hook-form"; +import { LeaveType } from "@/app/api/timesheets"; +import MobileLeaveEntry from "./MobileLeaveEntry"; +import DateHoursList from "../DateHoursTable/DateHoursList"; + +interface Props { + leaveTypes: LeaveType[]; + timesheetRecords: RecordTimesheetInput; +} + +const MobileLeaveTable: React.FC = ({ + timesheetRecords, + leaveTypes, +}) => { + const { watch } = useFormContext(); + const currentInput = watch(); + const days = Object.keys(currentInput); + + return ( + + ); +}; + +export default MobileLeaveTable; diff --git a/src/components/TimesheetModal/TimesheetModal.tsx b/src/components/TimesheetModal/TimesheetModal.tsx index c819fc1..0c0ead9 100644 --- a/src/components/TimesheetModal/TimesheetModal.tsx +++ b/src/components/TimesheetModal/TimesheetModal.tsx @@ -9,18 +9,23 @@ import { ModalProps, SxProps, Typography, + useMediaQuery, + useTheme, } from "@mui/material"; import TimesheetTable from "../TimesheetTable"; import { useTranslation } from "react-i18next"; import { Check, Close } from "@mui/icons-material"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import { + RecordLeaveInput, RecordTimesheetInput, saveTimesheet, } from "@/app/api/timesheets/actions"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; +import FullscreenModal from "../FullscreenModal"; +import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable"; interface Props { isOpen: boolean; @@ -29,6 +34,7 @@ interface Props { assignedProjects: AssignedProject[]; username: string; defaultTimesheets?: RecordTimesheetInput; + leaveRecords: RecordLeaveInput; } const modalSx: SxProps = { @@ -38,7 +44,7 @@ const modalSx: SxProps = { transform: "translate(-50%, -50%)", width: { xs: "calc(100% - 2rem)", sm: "90%" }, maxHeight: "90%", - maxWidth: 1200, + maxWidth: 1400, }; const TimesheetModal: React.FC = ({ @@ -48,6 +54,7 @@ const TimesheetModal: React.FC = ({ assignedProjects, username, defaultTimesheets, + leaveRecords, }) => { const { t } = useTranslation("home"); @@ -101,44 +108,76 @@ const TimesheetModal: React.FC = ({ [onClose], ); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.up("sm")); + return ( - - - - + {matches ? ( + // Desktop version + + + + + {t("Timesheet Input")} + + + + + + + + + + + + ) : ( + // Mobile version + + - + {t("Timesheet Input")} - - - - - - - - - - - + + + + )} + ); }; diff --git a/src/components/TimesheetTable/MobileTimesheetEntry.tsx b/src/components/TimesheetTable/MobileTimesheetEntry.tsx new file mode 100644 index 0000000..efaf465 --- /dev/null +++ b/src/components/TimesheetTable/MobileTimesheetEntry.tsx @@ -0,0 +1,40 @@ +import { TimeEntry, RecordTimesheetInput } from "@/app/api/timesheets/actions"; +import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; +import { Add, Edit } from "@mui/icons-material"; +import { + Box, + Button, + Card, + CardContent, + IconButton, + Typography, +} from "@mui/material"; +import dayjs from "dayjs"; +import React from "react"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; + +interface Props { + date: string; + allProjects: ProjectWithTasks[]; + assignedProjects: AssignedProject[]; +} + +const MobileTimesheetEntry: React.FC = ({ + date, + allProjects, + assignedProjects, +}) => { + const { + t, + i18n: { language }, + } = useTranslation("home"); + const dayJsObj = dayjs(date); + const { watch, setValue } = useFormContext(); + const currentEntries = watch(date); + + return null; +}; + +export default MobileTimesheetEntry; diff --git a/src/components/TimesheetTable/MobileTimesheetTable.tsx b/src/components/TimesheetTable/MobileTimesheetTable.tsx new file mode 100644 index 0000000..21fd3aa --- /dev/null +++ b/src/components/TimesheetTable/MobileTimesheetTable.tsx @@ -0,0 +1,37 @@ +import { + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import React from "react"; +import { useFormContext } from "react-hook-form"; +import DateHoursList from "../DateHoursTable/DateHoursList"; +import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; +import MobileTimesheetEntry from "./MobileTimesheetEntry"; + +interface Props { + allProjects: ProjectWithTasks[]; + assignedProjects: AssignedProject[]; + leaveRecords: RecordLeaveInput; +} + +const MobileTimesheetTable: React.FC = ({ + allProjects, + assignedProjects, + leaveRecords, +}) => { + const { watch } = useFormContext(); + const currentInput = watch(); + const days = Object.keys(currentInput); + + return ( + + ); +}; + +export default MobileTimesheetTable; diff --git a/src/components/TimesheetTable/TimesheetTable.tsx b/src/components/TimesheetTable/TimesheetTable.tsx index 4a39162..659c488 100644 --- a/src/components/TimesheetTable/TimesheetTable.tsx +++ b/src/components/TimesheetTable/TimesheetTable.tsx @@ -1,146 +1,36 @@ -import { RecordTimesheetInput, TimeEntry } from "@/app/api/timesheets/actions"; -import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; -import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material"; import { - Box, - Collapse, - IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; -import dayjs from "dayjs"; -import React, { useState } from "react"; + RecordLeaveInput, + RecordTimesheetInput, +} from "@/app/api/timesheets/actions"; +import React from "react"; import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; import EntryInputTable from "./EntryInputTable"; import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; -import { TIMESHEET_DAILY_MAX_HOURS } from "@/app/api/timesheets/utils"; +import DateHoursTable from "../DateHoursTable"; interface Props { allProjects: ProjectWithTasks[]; assignedProjects: AssignedProject[]; + leaveRecords: RecordLeaveInput; } -const TimesheetTable: React.FC = ({ allProjects, assignedProjects }) => { - const { t } = useTranslation("home"); - +const TimesheetTable: React.FC = ({ + allProjects, + assignedProjects, + leaveRecords, +}) => { const { watch } = useFormContext(); const currentInput = watch(); const days = Object.keys(currentInput); return ( - - - - - - {t("Date")} - {t("Daily Total Hours")} - - - - {days.map((day, index) => { - const entries = currentInput[day]; - return ( - - ); - })} - -
-
- ); -}; - -const DayRow: React.FC<{ - day: string; - entries: TimeEntry[]; - allProjects: ProjectWithTasks[]; - assignedProjects: AssignedProject[]; -}> = ({ day, entries, allProjects, assignedProjects }) => { - const { - t, - i18n: { language }, - } = useTranslation("home"); - const dayJsObj = dayjs(day); - const [open, setOpen] = useState(false); - - const totalHours = entries.reduce( - (acc, entry) => acc + (entry.inputHours || 0) + (entry.otHours || 0), - 0, - ); - - return ( - <> - - - setOpen(!open)} - > - {open ? : } - - - - {shortDateFormatter(language).format(dayJsObj.toDate())} - - TIMESHEET_DAILY_MAX_HOURS ? "error.main" : undefined, - }} - > - {manhourFormatter.format(totalHours)} - {totalHours > TIMESHEET_DAILY_MAX_HOURS && ( - - {t("(the daily total hours cannot be more than {{hours}})", { - hours: TIMESHEET_DAILY_MAX_HOURS, - })} - - )} - - - - - - - - - - - - + ); }; diff --git a/src/components/UserWorkspacePage/UserWorkspacePage.tsx b/src/components/UserWorkspacePage/UserWorkspacePage.tsx index e5a1d1a..062e233 100644 --- a/src/components/UserWorkspacePage/UserWorkspacePage.tsx +++ b/src/components/UserWorkspacePage/UserWorkspacePage.tsx @@ -88,12 +88,14 @@ const UserWorkspacePage: React.FC = ({ assignedProjects={assignedProjects} username={username} defaultTimesheets={defaultTimesheets} + leaveRecords={defaultLeaveRecords} /> {assignedProjects.length > 0 ? (