|
- 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 {
- DAILY_NORMAL_MAX_HOURS,
- LEAVE_DAILY_MAX_HOURS,
- TIMESHEET_DAILY_MAX_HOURS,
- } from "@/app/api/timesheets/utils";
- import { HolidaysResult } from "@/app/api/holidays";
- import { getHolidayForDate } from "@/app/utils/holidayUtils";
-
- interface Props<EntryComponentProps = object> {
- days: string[];
- companyHolidays: HolidaysResult[];
- leaveEntries: RecordLeaveInput;
- timesheetEntries: RecordTimesheetInput;
- EntryComponent: React.FunctionComponent<
- EntryComponentProps & { date: string }
- >;
- entryComponentProps: EntryComponentProps;
- errorComponent?: React.ReactNode;
- }
-
- function DateHoursList<EntryTableProps>({
- days,
- leaveEntries,
- timesheetEntries,
- EntryComponent,
- entryComponentProps,
- companyHolidays,
- errorComponent,
- }: Props<EntryTableProps>) {
- const {
- t,
- i18n: { language },
- } = useTranslation("home");
-
- const [selectedDate, setSelectedDate] = useState("");
- const isDateSelected = selectedDate !== "";
-
- const makeSelectDate = useCallback(
- (date: string) => () => {
- setSelectedDate(date);
- },
- [],
- );
-
- const onDateDone = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
- (e) => {
- setSelectedDate("");
- e.preventDefault();
- },
- [],
- );
-
- return (
- <>
- {isDateSelected ? (
- <EntryComponent date={selectedDate} {...entryComponentProps} />
- ) : (
- <Box overflow="scroll" flex={1}>
- {days.map((day, index) => {
- const dayJsObj = dayjs(day);
-
- const holiday = getHolidayForDate(day, companyHolidays);
- const isHoliday =
- holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6;
-
- const leaves = leaveEntries[day];
- const leaveHours =
- leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0;
-
- const timesheet = timesheetEntries[day];
- const timesheetNormalHours =
- timesheet?.reduce(
- (acc, entry) => acc + (entry.inputHours || 0),
- 0,
- ) || 0;
- const timesheetOtHours =
- timesheet?.reduce(
- (acc, entry) => acc + (entry.otHours || 0),
- 0,
- ) || 0;
- const timesheetHours = timesheetNormalHours + timesheetOtHours;
-
- const dailyTotal = leaveHours + timesheetHours;
-
- const normalHoursExceeded =
- timesheetNormalHours > DAILY_NORMAL_MAX_HOURS;
- const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS;
- const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS;
-
- return (
- <Card
- key={`${day}-${index}`}
- sx={{ marginBlockEnd: 2, marginInline: 2 }}
- >
- <CardActionArea onClick={makeSelectDate(day)}>
- <CardContent sx={{ padding: 3 }}>
- <Typography
- variant="overline"
- component="div"
- sx={{
- color: isHoliday ? "error.main" : undefined,
- }}
- >
- {shortDateFormatter(language).format(dayJsObj.toDate())}
- {holiday && (
- <Typography
- marginInlineStart={1}
- variant="caption"
- >{`(${holiday.title})`}</Typography>
- )}
- </Typography>
- <Stack spacing={1}>
- <Box
- sx={{
- display: "flex",
- justifyContent: "space-between",
- flexWrap: "wrap",
- alignItems: "baseline",
- color: normalHoursExceeded ? "error.main" : undefined,
- }}
- >
- <Typography variant="body2">
- {t("Timesheet Hours")}
- </Typography>
- <Typography>
- {manhourFormatter.format(timesheetHours)}
- </Typography>
- {normalHoursExceeded && (
- <Typography
- component="div"
- width="100%"
- variant="caption"
- paddingInlineEnd="40%"
- >
- {t(
- "The daily normal hours cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours.",
- {
- DAILY_NORMAL_MAX_HOURS,
- },
- )}
- </Typography>
- )}
- </Box>
- <Box
- sx={{
- display: "flex",
- justifyContent: "space-between",
- flexWrap: "wrap",
- alignItems: "baseline",
- color: leaveExceeded ? "error.main" : undefined,
- }}
- >
- <Typography variant="body2">
- {t("Leave Hours")}
- </Typography>
- <Typography>
- {manhourFormatter.format(leaveHours)}
- </Typography>
- {leaveExceeded && (
- <Typography
- component="div"
- width="100%"
- variant="caption"
- >
- {t("Leave hours cannot be more than {{hours}}", {
- hours: LEAVE_DAILY_MAX_HOURS,
- })}
- </Typography>
- )}
- </Box>
-
- <Box
- sx={{
- display: "flex",
- justifyContent: "space-between",
- flexWrap: "wrap",
- alignItems: "baseline",
- color: dailyTotalExceeded ? "error.main" : undefined,
- }}
- >
- <Typography variant="body2">
- {t("Daily Total Hours")}
- </Typography>
- <Typography>
- {manhourFormatter.format(timesheetHours + leaveHours)}
- </Typography>
- {dailyTotalExceeded && (
- <Typography
- component="div"
- width="100%"
- variant="caption"
- >
- {t(
- "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}",
- {
- TIMESHEET_DAILY_MAX_HOURS,
- },
- )}
- </Typography>
- )}
- </Box>
- </Stack>
- </CardContent>
- </CardActionArea>
- </Card>
- );
- })}
- </Box>
- )}
- {errorComponent}
- <Box padding={2} display="flex" justifyContent="flex-end">
- {isDateSelected ? (
- <Button
- variant="outlined"
- startIcon={<ArrowBack />}
- onClick={onDateDone}
- >
- {t("Done")}
- </Button>
- ) : (
- <Button variant="contained" startIcon={<Check />} type="submit">
- {t("Save")}
- </Button>
- )}
- </Box>
- </>
- );
- }
-
- export default DateHoursList;
|