| @@ -1,4 +1,13 @@ | |||||
| import { LeaveEntry, TimeEntry } from "./actions"; | |||||
| import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||||
| import { HolidaysResult } from "../holidays"; | |||||
| import { | |||||
| LeaveEntry, | |||||
| RecordLeaveInput, | |||||
| RecordTimesheetInput, | |||||
| TimeEntry, | |||||
| } from "./actions"; | |||||
| import { convertDateArrayToString } from "@/app/utils/formatUtil"; | |||||
| import compact from "lodash/compact"; | |||||
| export type TimeEntryError = { | export type TimeEntryError = { | ||||
| [field in keyof TimeEntry]?: string; | [field in keyof TimeEntry]?: string; | ||||
| @@ -6,7 +15,7 @@ export type TimeEntryError = { | |||||
| /** | /** | ||||
| * @param entry - the time entry | * @param entry - the time entry | ||||
| * @returns the field where there is an error, or an empty string if there is none | |||||
| * @returns an object where the keys are the error fields and the values the error message, and undefined if there are no errors | |||||
| */ | */ | ||||
| export const validateTimeEntry = ( | export const validateTimeEntry = ( | ||||
| entry: Partial<TimeEntry>, | entry: Partial<TimeEntry>, | ||||
| @@ -58,6 +67,61 @@ export const isValidLeaveEntry = (entry: Partial<LeaveEntry>): string => { | |||||
| return error; | return error; | ||||
| }; | }; | ||||
| export const validateTimesheet = ( | |||||
| timesheet: RecordTimesheetInput, | |||||
| leaveRecords: RecordLeaveInput, | |||||
| companyHolidays: HolidaysResult[], | |||||
| ): { [date: string]: string } | undefined => { | |||||
| const errors: { [date: string]: string } = {}; | |||||
| const holidays = new Set( | |||||
| compact([ | |||||
| ...getPublicHolidaysForNYears(2).map((h) => h.date), | |||||
| ...companyHolidays.map((h) => convertDateArrayToString(h.date)), | |||||
| ]), | |||||
| ); | |||||
| Object.keys(timesheet).forEach((date) => { | |||||
| const timeEntries = timesheet[date]; | |||||
| // Check each entry | |||||
| for (const entry of timeEntries) { | |||||
| const entryErrors = validateTimeEntry(entry, holidays.has(date)); | |||||
| if (entryErrors) { | |||||
| errors[date] = "There are errors in the entries"; | |||||
| return; | |||||
| } | |||||
| } | |||||
| // Check total hours | |||||
| const leaves = leaveRecords[date]; | |||||
| const leaveHours = | |||||
| leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | |||||
| const totalNormalHours = timeEntries.reduce((acc, entry) => { | |||||
| return acc + (entry.inputHours || 0); | |||||
| }, 0); | |||||
| const totalOtHours = timeEntries.reduce((acc, entry) => { | |||||
| return acc + (entry.otHours || 0); | |||||
| }, 0); | |||||
| if (totalNormalHours > DAILY_NORMAL_MAX_HOURS) { | |||||
| errors[date] = | |||||
| "The daily normal hours cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours."; | |||||
| } else if ( | |||||
| totalNormalHours + totalOtHours + leaveHours > | |||||
| TIMESHEET_DAILY_MAX_HOURS | |||||
| ) { | |||||
| errors[date] = | |||||
| "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}"; | |||||
| } | |||||
| }); | |||||
| return Object.keys(errors).length > 0 ? errors : undefined; | |||||
| }; | |||||
| export const DAILY_NORMAL_MAX_HOURS = 8; | export const DAILY_NORMAL_MAX_HOURS = 8; | ||||
| export const LEAVE_DAILY_MAX_HOURS = 8; | export const LEAVE_DAILY_MAX_HOURS = 8; | ||||
| export const TIMESHEET_DAILY_MAX_HOURS = 20; | export const TIMESHEET_DAILY_MAX_HOURS = 20; | ||||
| @@ -17,6 +17,7 @@ import dayjs from "dayjs"; | |||||
| import React, { useCallback, useState } from "react"; | import React, { useCallback, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { | import { | ||||
| DAILY_NORMAL_MAX_HOURS, | |||||
| LEAVE_DAILY_MAX_HOURS, | LEAVE_DAILY_MAX_HOURS, | ||||
| TIMESHEET_DAILY_MAX_HOURS, | TIMESHEET_DAILY_MAX_HOURS, | ||||
| } from "@/app/api/timesheets/utils"; | } from "@/app/api/timesheets/utils"; | ||||
| @@ -32,6 +33,7 @@ interface Props<EntryComponentProps = object> { | |||||
| EntryComponentProps & { date: string } | EntryComponentProps & { date: string } | ||||
| >; | >; | ||||
| entryComponentProps: EntryComponentProps; | entryComponentProps: EntryComponentProps; | ||||
| errorComponent?: React.ReactNode; | |||||
| } | } | ||||
| function DateHoursList<EntryTableProps>({ | function DateHoursList<EntryTableProps>({ | ||||
| @@ -41,6 +43,7 @@ function DateHoursList<EntryTableProps>({ | |||||
| EntryComponent, | EntryComponent, | ||||
| entryComponentProps, | entryComponentProps, | ||||
| companyHolidays, | companyHolidays, | ||||
| errorComponent, | |||||
| }: Props<EntryTableProps>) { | }: Props<EntryTableProps>) { | ||||
| const { | const { | ||||
| t, | t, | ||||
| @@ -83,15 +86,22 @@ function DateHoursList<EntryTableProps>({ | |||||
| leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | ||||
| const timesheet = timesheetEntries[day]; | const timesheet = timesheetEntries[day]; | ||||
| const timesheetHours = | |||||
| const timesheetNormalHours = | |||||
| timesheet?.reduce( | timesheet?.reduce( | ||||
| (acc, entry) => | |||||
| acc + (entry.inputHours || 0) + (entry.otHours || 0), | |||||
| (acc, entry) => acc + (entry.inputHours || 0), | |||||
| 0, | 0, | ||||
| ) || 0; | ) || 0; | ||||
| const timesheetOtHours = | |||||
| timesheet?.reduce( | |||||
| (acc, entry) => acc + (entry.otHours || 0), | |||||
| 0, | |||||
| ) || 0; | |||||
| const timesheetHours = timesheetNormalHours + timesheetOtHours; | |||||
| const dailyTotal = leaveHours + timesheetHours; | const dailyTotal = leaveHours + timesheetHours; | ||||
| const normalHoursExceeded = | |||||
| timesheetNormalHours > DAILY_NORMAL_MAX_HOURS; | |||||
| const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | ||||
| const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | ||||
| @@ -122,7 +132,9 @@ function DateHoursList<EntryTableProps>({ | |||||
| sx={{ | sx={{ | ||||
| display: "flex", | display: "flex", | ||||
| justifyContent: "space-between", | justifyContent: "space-between", | ||||
| flexWrap: "wrap", | |||||
| alignItems: "baseline", | alignItems: "baseline", | ||||
| color: normalHoursExceeded ? "error.main" : undefined, | |||||
| }} | }} | ||||
| > | > | ||||
| <Typography variant="body2"> | <Typography variant="body2"> | ||||
| @@ -131,6 +143,21 @@ function DateHoursList<EntryTableProps>({ | |||||
| <Typography> | <Typography> | ||||
| {manhourFormatter.format(timesheetHours)} | {manhourFormatter.format(timesheetHours)} | ||||
| </Typography> | </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> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| @@ -182,9 +209,9 @@ function DateHoursList<EntryTableProps>({ | |||||
| variant="caption" | variant="caption" | ||||
| > | > | ||||
| {t( | {t( | ||||
| "The daily total hours cannot be more than {{hours}}", | |||||
| "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}", | |||||
| { | { | ||||
| hours: TIMESHEET_DAILY_MAX_HOURS, | |||||
| TIMESHEET_DAILY_MAX_HOURS, | |||||
| }, | }, | ||||
| )} | )} | ||||
| </Typography> | </Typography> | ||||
| @@ -198,6 +225,7 @@ function DateHoursList<EntryTableProps>({ | |||||
| })} | })} | ||||
| </Box> | </Box> | ||||
| )} | )} | ||||
| {errorComponent} | |||||
| <Box padding={2} display="flex" justifyContent="flex-end"> | <Box padding={2} display="flex" justifyContent="flex-end"> | ||||
| {isDateSelected ? ( | {isDateSelected ? ( | ||||
| <Button | <Button | ||||
| @@ -21,6 +21,7 @@ import dayjs from "dayjs"; | |||||
| import React, { useState } from "react"; | import React, { useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { | import { | ||||
| DAILY_NORMAL_MAX_HOURS, | |||||
| LEAVE_DAILY_MAX_HOURS, | LEAVE_DAILY_MAX_HOURS, | ||||
| TIMESHEET_DAILY_MAX_HOURS, | TIMESHEET_DAILY_MAX_HOURS, | ||||
| } from "@/app/api/timesheets/utils"; | } from "@/app/api/timesheets/utils"; | ||||
| @@ -112,14 +113,15 @@ function DayRow<EntryTableProps>({ | |||||
| leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | ||||
| const timesheet = timesheetEntries[day]; | const timesheet = timesheetEntries[day]; | ||||
| const timesheetHours = | |||||
| timesheet?.reduce( | |||||
| (acc, entry) => acc + (entry.inputHours || 0) + (entry.otHours || 0), | |||||
| 0, | |||||
| ) || 0; | |||||
| 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 dailyTotal = leaveHours + timesheetHours; | ||||
| const normalHoursExceeded = timesheetNormalHours > DAILY_NORMAL_MAX_HOURS; | |||||
| const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | ||||
| const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | ||||
| @@ -146,7 +148,27 @@ function DayRow<EntryTableProps>({ | |||||
| )} | )} | ||||
| </TableCell> | </TableCell> | ||||
| {/* Timesheet */} | {/* Timesheet */} | ||||
| <TableCell>{manhourFormatter.format(timesheetHours)}</TableCell> | |||||
| <TableCell | |||||
| sx={{ | |||||
| color: normalHoursExceeded ? "error.main" : undefined, | |||||
| }} | |||||
| > | |||||
| <Box display="flex" gap={1} alignItems="center"> | |||||
| {manhourFormatter.format(timesheetHours)} | |||||
| {normalHoursExceeded && ( | |||||
| <Tooltip | |||||
| title={t( | |||||
| "The daily normal hours cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours.", | |||||
| { | |||||
| DAILY_NORMAL_MAX_HOURS, | |||||
| }, | |||||
| )} | |||||
| > | |||||
| <Info fontSize="small" /> | |||||
| </Tooltip> | |||||
| )} | |||||
| </Box> | |||||
| </TableCell> | |||||
| {/* Leave total */} | {/* Leave total */} | ||||
| <TableCell | <TableCell | ||||
| sx={{ | sx={{ | ||||
| @@ -177,9 +199,9 @@ function DayRow<EntryTableProps>({ | |||||
| {dailyTotalExceeded && ( | {dailyTotalExceeded && ( | ||||
| <Tooltip | <Tooltip | ||||
| title={t( | title={t( | ||||
| "The daily total hours cannot be more than {{hours}}", | |||||
| "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}", | |||||
| { | { | ||||
| hours: TIMESHEET_DAILY_MAX_HOURS, | |||||
| TIMESHEET_DAILY_MAX_HOURS, | |||||
| }, | }, | ||||
| )} | )} | ||||
| > | > | ||||
| @@ -0,0 +1,28 @@ | |||||
| import { Alert, AlertTitle, Box } from "@mui/material"; | |||||
| import compact from "lodash/compact"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| interface Props { | |||||
| errors: (string | undefined)[]; | |||||
| } | |||||
| const ErrorAlert: React.FC<Props> = ({ errors }) => { | |||||
| const { t } = useTranslation("common"); | |||||
| if (compact(errors).length === 0) return null; | |||||
| return ( | |||||
| <Alert severity="error"> | |||||
| <AlertTitle>{t("There are some errors")}</AlertTitle> | |||||
| <Box component="ul"> | |||||
| {errors.map((error, index) => ( | |||||
| <Box component="li" key={`${error}-${index}`}> | |||||
| {error} | |||||
| </Box> | |||||
| ))} | |||||
| </Box> | |||||
| </Alert> | |||||
| ); | |||||
| }; | |||||
| export default ErrorAlert; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./ErrorAlert"; | |||||
| @@ -101,9 +101,15 @@ const LeaveEditModal: React.FC<Props> = ({ | |||||
| fullWidth | fullWidth | ||||
| {...register("inputHours", { | {...register("inputHours", { | ||||
| setValueAs: (value) => roundToNearestQuarter(parseFloat(value)), | setValueAs: (value) => roundToNearestQuarter(parseFloat(value)), | ||||
| validate: (value) => 0 < value && value <= LEAVE_DAILY_MAX_HOURS, | |||||
| validate: (value) => | |||||
| (0 < value && value <= LEAVE_DAILY_MAX_HOURS) || | |||||
| t( | |||||
| "Input hours should be between 0 and {{LEAVE_DAILY_MAX_HOURS}}", | |||||
| { LEAVE_DAILY_MAX_HOURS }, | |||||
| ), | |||||
| })} | })} | ||||
| error={Boolean(formState.errors.inputHours)} | error={Boolean(formState.errors.inputHours)} | ||||
| helperText={formState.errors.inputHours?.message} | |||||
| /> | /> | ||||
| <TextField | <TextField | ||||
| label={t("Remark")} | label={t("Remark")} | ||||
| @@ -26,6 +26,12 @@ import FullscreenModal from "../FullscreenModal"; | |||||
| import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable"; | import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable"; | ||||
| import useIsMobile from "@/app/utils/useIsMobile"; | import useIsMobile from "@/app/utils/useIsMobile"; | ||||
| import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
| import { | |||||
| DAILY_NORMAL_MAX_HOURS, | |||||
| TIMESHEET_DAILY_MAX_HOURS, | |||||
| validateTimesheet, | |||||
| } from "@/app/api/timesheets/utils"; | |||||
| import ErrorAlert from "../ErrorAlert"; | |||||
| interface Props { | interface Props { | ||||
| isOpen: boolean; | isOpen: boolean; | ||||
| @@ -77,6 +83,15 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
| const onSubmit = useCallback<SubmitHandler<RecordTimesheetInput>>( | const onSubmit = useCallback<SubmitHandler<RecordTimesheetInput>>( | ||||
| async (data) => { | async (data) => { | ||||
| const errors = validateTimesheet(data, leaveRecords, companyHolidays); | |||||
| if (errors) { | |||||
| Object.keys(errors).forEach((date) => | |||||
| formProps.setError(date, { | |||||
| message: errors[date], | |||||
| }), | |||||
| ); | |||||
| return; | |||||
| } | |||||
| const savedRecords = await saveTimesheet(data, username); | const savedRecords = await saveTimesheet(data, username); | ||||
| const today = dayjs(); | const today = dayjs(); | ||||
| @@ -93,7 +108,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
| formProps.reset(newFormValues); | formProps.reset(newFormValues); | ||||
| onClose(); | onClose(); | ||||
| }, | }, | ||||
| [formProps, onClose, username], | |||||
| [companyHolidays, formProps, leaveRecords, onClose, username], | |||||
| ); | ); | ||||
| const onCancel = useCallback(() => { | const onCancel = useCallback(() => { | ||||
| @@ -110,6 +125,20 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
| [onClose], | [onClose], | ||||
| ); | ); | ||||
| const errorComponent = ( | |||||
| <ErrorAlert | |||||
| errors={Object.keys(formProps.formState.errors).map((date) => { | |||||
| const error = formProps.formState.errors[date]?.message; | |||||
| return error | |||||
| ? `${date}: ${t(error, { | |||||
| TIMESHEET_DAILY_MAX_HOURS, | |||||
| DAILY_NORMAL_MAX_HOURS, | |||||
| })}` | |||||
| : undefined; | |||||
| })} | |||||
| /> | |||||
| ); | |||||
| const matches = useIsMobile(); | const matches = useIsMobile(); | ||||
| return ( | return ( | ||||
| @@ -138,6 +167,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
| leaveRecords={leaveRecords} | leaveRecords={leaveRecords} | ||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| {errorComponent} | |||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| @@ -176,6 +206,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
| assignedProjects={assignedProjects} | assignedProjects={assignedProjects} | ||||
| allProjects={allProjects} | allProjects={allProjects} | ||||
| leaveRecords={leaveRecords} | leaveRecords={leaveRecords} | ||||
| errorComponent={errorComponent} | |||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| </FullscreenModal> | </FullscreenModal> | ||||
| @@ -89,7 +89,8 @@ const EntryInputTable: React.FC<Props> = ({ | |||||
| }, {}); | }, {}); | ||||
| }, [assignedProjects]); | }, [assignedProjects]); | ||||
| const { getValues, setValue } = useFormContext<RecordTimesheetInput>(); | |||||
| const { getValues, setValue, clearErrors } = | |||||
| useFormContext<RecordTimesheetInput>(); | |||||
| const currentEntries = getValues(day); | const currentEntries = getValues(day); | ||||
| const [entries, setEntries] = useState<TimeEntryRow[]>(currentEntries || []); | const [entries, setEntries] = useState<TimeEntryRow[]>(currentEntries || []); | ||||
| @@ -398,7 +399,8 @@ const EntryInputTable: React.FC<Props> = ({ | |||||
| ...entry, | ...entry, | ||||
| })), | })), | ||||
| ]); | ]); | ||||
| }, [getValues, entries, setValue, day]); | |||||
| clearErrors(day); | |||||
| }, [getValues, entries, setValue, day, clearErrors]); | |||||
| const hasOutOfPlannedStages = entries.some( | const hasOutOfPlannedStages = entries.some( | ||||
| (entry) => entry.isPlanned !== undefined && !entry.isPlanned, | (entry) => entry.isPlanned !== undefined && !entry.isPlanned, | ||||
| @@ -51,7 +51,7 @@ const MobileTimesheetEntry: React.FC<Props> = ({ | |||||
| const holiday = getHolidayForDate(date, companyHolidays); | const holiday = getHolidayForDate(date, companyHolidays); | ||||
| const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | ||||
| const { watch, setValue } = useFormContext<RecordTimesheetInput>(); | |||||
| const { watch, setValue, clearErrors } = useFormContext<RecordTimesheetInput>(); | |||||
| const currentEntries = watch(date); | const currentEntries = watch(date); | ||||
| // Edit modal | // Edit modal | ||||
| @@ -70,13 +70,14 @@ const MobileTimesheetEntry: React.FC<Props> = ({ | |||||
| date, | date, | ||||
| currentEntries.filter((entry) => entry.id !== defaultValues.id), | currentEntries.filter((entry) => entry.id !== defaultValues.id), | ||||
| ); | ); | ||||
| clearErrors(date); | |||||
| setEditModalOpen(false); | setEditModalOpen(false); | ||||
| } | } | ||||
| : undefined, | : undefined, | ||||
| }); | }); | ||||
| setEditModalOpen(true); | setEditModalOpen(true); | ||||
| }, | }, | ||||
| [currentEntries, date, setValue], | |||||
| [clearErrors, currentEntries, date, setValue], | |||||
| ); | ); | ||||
| const closeEditModal = useCallback(() => { | const closeEditModal = useCallback(() => { | ||||
| @@ -93,12 +94,13 @@ const MobileTimesheetEntry: React.FC<Props> = ({ | |||||
| ...(e.id === existingEntry.id ? entry : e), | ...(e.id === existingEntry.id ? entry : e), | ||||
| })), | })), | ||||
| ); | ); | ||||
| clearErrors(date); | |||||
| } else { | } else { | ||||
| setValue(date, [...currentEntries, entry]); | setValue(date, [...currentEntries, entry]); | ||||
| } | } | ||||
| setEditModalOpen(false); | setEditModalOpen(false); | ||||
| }, | }, | ||||
| [currentEntries, date, setValue], | |||||
| [clearErrors, currentEntries, date, setValue], | |||||
| ); | ); | ||||
| return ( | return ( | ||||
| @@ -14,6 +14,7 @@ interface Props { | |||||
| assignedProjects: AssignedProject[]; | assignedProjects: AssignedProject[]; | ||||
| leaveRecords: RecordLeaveInput; | leaveRecords: RecordLeaveInput; | ||||
| companyHolidays: HolidaysResult[]; | companyHolidays: HolidaysResult[]; | ||||
| errorComponent?: React.ReactNode; | |||||
| } | } | ||||
| const MobileTimesheetTable: React.FC<Props> = ({ | const MobileTimesheetTable: React.FC<Props> = ({ | ||||
| @@ -21,6 +22,7 @@ const MobileTimesheetTable: React.FC<Props> = ({ | |||||
| assignedProjects, | assignedProjects, | ||||
| leaveRecords, | leaveRecords, | ||||
| companyHolidays, | companyHolidays, | ||||
| errorComponent, | |||||
| }) => { | }) => { | ||||
| const { watch } = useFormContext<RecordTimesheetInput>(); | const { watch } = useFormContext<RecordTimesheetInput>(); | ||||
| const currentInput = watch(); | const currentInput = watch(); | ||||
| @@ -34,6 +36,7 @@ const MobileTimesheetTable: React.FC<Props> = ({ | |||||
| timesheetEntries={currentInput} | timesheetEntries={currentInput} | ||||
| EntryComponent={MobileTimesheetEntry} | EntryComponent={MobileTimesheetEntry} | ||||
| entryComponentProps={{ allProjects, assignedProjects, companyHolidays }} | entryComponentProps={{ allProjects, assignedProjects, companyHolidays }} | ||||
| errorComponent={errorComponent} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||