| @@ -13,10 +13,6 @@ export type TimeEntryError = { | |||
| [field in keyof TimeEntry]?: string; | |||
| }; | |||
| interface TimeEntryValidationOptions { | |||
| skipTaskValidation?: boolean; | |||
| } | |||
| /** | |||
| * @param entry - the time entry | |||
| * @returns an object where the keys are the error fields and the values the error message, and undefined if there are no errors | |||
| @@ -24,7 +20,6 @@ interface TimeEntryValidationOptions { | |||
| export const validateTimeEntry = ( | |||
| entry: Partial<TimeEntry>, | |||
| isHoliday: boolean, | |||
| options: TimeEntryValidationOptions = {}, | |||
| ): TimeEntryError | undefined => { | |||
| // Test for errors | |||
| const error: TimeEntryError = {}; | |||
| @@ -46,12 +41,10 @@ export const validateTimeEntry = ( | |||
| // If there is a project id, there should also be taskGroupId, taskId, inputHours | |||
| if (entry.projectId) { | |||
| if (!options.skipTaskValidation) { | |||
| if (!entry.taskGroupId) { | |||
| error.taskGroupId = "Required"; | |||
| } else if (!entry.taskId) { | |||
| error.taskId = "Required"; | |||
| } | |||
| if (!entry.taskGroupId) { | |||
| error.taskGroupId = "Required"; | |||
| } else if (!entry.taskId) { | |||
| error.taskId = "Required"; | |||
| } | |||
| } else { | |||
| if (!entry.remark) { | |||
| @@ -78,7 +71,6 @@ export const validateTimesheet = ( | |||
| timesheet: RecordTimesheetInput, | |||
| leaveRecords: RecordLeaveInput, | |||
| companyHolidays: HolidaysResult[], | |||
| options: TimeEntryValidationOptions = {}, | |||
| ): { [date: string]: string } | undefined => { | |||
| const errors: { [date: string]: string } = {}; | |||
| @@ -94,7 +86,7 @@ export const validateTimesheet = ( | |||
| // Check each entry | |||
| for (const entry of timeEntries) { | |||
| const entryErrors = validateTimeEntry(entry, holidays.has(date), options); | |||
| const entryErrors = validateTimeEntry(entry, holidays.has(date)); | |||
| if (entryErrors) { | |||
| errors[date] = "There are errors in the entries"; | |||
| @@ -107,7 +99,52 @@ export const validateTimesheet = ( | |||
| const leaveHours = | |||
| leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0; | |||
| const totalNormalHours = timeEntries.reduce((acc, entry) => { | |||
| const totalInputHours = timeEntries.reduce((acc, entry) => { | |||
| return acc + (entry.inputHours || 0); | |||
| }, 0); | |||
| const totalOtHours = timeEntries.reduce((acc, entry) => { | |||
| return acc + (entry.otHours || 0); | |||
| }, 0); | |||
| if (totalInputHours + leaveHours > DAILY_NORMAL_MAX_HOURS) { | |||
| errors[date] = | |||
| "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 ( | |||
| totalInputHours + 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 validateLeaveRecord = ( | |||
| leaveRecords: RecordLeaveInput, | |||
| timesheet: RecordTimesheetInput, | |||
| ): { [date: string]: string } | undefined => { | |||
| const errors: { [date: string]: string } = {}; | |||
| Object.keys(leaveRecords).forEach((date) => { | |||
| const leaves = leaveRecords[date]; | |||
| // Check each leave entry | |||
| for (const entry of leaves) { | |||
| const entryError = isValidLeaveEntry(entry); | |||
| if (entryError) { | |||
| errors[date] = "There are errors in the entries"; | |||
| } | |||
| } | |||
| // Check total hours | |||
| const timeEntries = timesheet[date] || []; | |||
| const leaveHours = leaves.reduce((acc, entry) => acc + entry.inputHours, 0); | |||
| const totalInputHours = timeEntries.reduce((acc, entry) => { | |||
| return acc + (entry.inputHours || 0); | |||
| }, 0); | |||
| @@ -115,11 +152,11 @@ export const validateTimesheet = ( | |||
| return acc + (entry.otHours || 0); | |||
| }, 0); | |||
| if (totalNormalHours > DAILY_NORMAL_MAX_HOURS) { | |||
| if (totalInputHours + leaveHours > 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."; | |||
| "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 ( | |||
| totalNormalHours + totalOtHours + leaveHours > | |||
| totalInputHours + totalOtHours + leaveHours > | |||
| TIMESHEET_DAILY_MAX_HOURS | |||
| ) { | |||
| errors[date] = | |||
| @@ -18,7 +18,6 @@ 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"; | |||
| @@ -101,8 +100,7 @@ function DateHoursList<EntryTableProps>({ | |||
| const dailyTotal = leaveHours + timesheetHours; | |||
| const normalHoursExceeded = | |||
| timesheetNormalHours > DAILY_NORMAL_MAX_HOURS; | |||
| const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | |||
| timesheetNormalHours + leaveHours > DAILY_NORMAL_MAX_HOURS; | |||
| const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | |||
| return ( | |||
| @@ -148,11 +146,12 @@ function DateHoursList<EntryTableProps>({ | |||
| 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.", | |||
| "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}} (timesheet hours: {{timesheetNormalHours}}, leave hours: {{leaveHours}}). Please use other hours for exceeding hours or decrease the leave hours.", | |||
| { | |||
| timesheetNormalHours, | |||
| leaveHours, | |||
| DAILY_NORMAL_MAX_HOURS, | |||
| }, | |||
| )} | |||
| @@ -165,7 +164,7 @@ function DateHoursList<EntryTableProps>({ | |||
| justifyContent: "space-between", | |||
| flexWrap: "wrap", | |||
| alignItems: "baseline", | |||
| color: leaveExceeded ? "error.main" : undefined, | |||
| color: normalHoursExceeded ? "error.main" : undefined, | |||
| }} | |||
| > | |||
| <Typography variant="body2"> | |||
| @@ -174,15 +173,20 @@ function DateHoursList<EntryTableProps>({ | |||
| <Typography> | |||
| {manhourFormatter.format(leaveHours)} | |||
| </Typography> | |||
| {leaveExceeded && ( | |||
| {normalHoursExceeded && ( | |||
| <Typography | |||
| component="div" | |||
| width="100%" | |||
| variant="caption" | |||
| > | |||
| {t("Leave hours cannot be more than {{hours}}", { | |||
| hours: LEAVE_DAILY_MAX_HOURS, | |||
| })} | |||
| {t( | |||
| "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}} (timesheet hours: {{timesheetNormalHours}}, leave hours: {{leaveHours}}). Please use other hours for exceeding hours or decrease the leave hours.", | |||
| { | |||
| timesheetNormalHours, | |||
| leaveHours, | |||
| DAILY_NORMAL_MAX_HOURS, | |||
| }, | |||
| )} | |||
| </Typography> | |||
| )} | |||
| </Box> | |||
| @@ -22,7 +22,6 @@ import React, { 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"; | |||
| @@ -121,8 +120,8 @@ function DayRow<EntryTableProps>({ | |||
| const dailyTotal = leaveHours + timesheetHours; | |||
| const normalHoursExceeded = timesheetNormalHours > DAILY_NORMAL_MAX_HOURS; | |||
| const leaveExceeded = leaveHours > LEAVE_DAILY_MAX_HOURS; | |||
| const normalHoursExceeded = | |||
| timesheetNormalHours + leaveHours > DAILY_NORMAL_MAX_HOURS; | |||
| const dailyTotalExceeded = dailyTotal > TIMESHEET_DAILY_MAX_HOURS; | |||
| return ( | |||
| @@ -158,8 +157,10 @@ function DayRow<EntryTableProps>({ | |||
| {normalHoursExceeded && ( | |||
| <Tooltip | |||
| title={t( | |||
| "The daily normal hours cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours.", | |||
| "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}} (timesheet hours: {{timesheetNormalHours}}, leave hours: {{leaveHours}}). Please use other hours for exceeding hours or decrease the leave hours.", | |||
| { | |||
| timesheetNormalHours, | |||
| leaveHours, | |||
| DAILY_NORMAL_MAX_HOURS, | |||
| }, | |||
| )} | |||
| @@ -172,16 +173,21 @@ function DayRow<EntryTableProps>({ | |||
| {/* Leave total */} | |||
| <TableCell | |||
| sx={{ | |||
| color: leaveExceeded ? "error.main" : undefined, | |||
| color: normalHoursExceeded ? "error.main" : undefined, | |||
| }} | |||
| > | |||
| <Box display="flex" gap={1} alignItems="center"> | |||
| {manhourFormatter.format(leaveHours)} | |||
| {leaveExceeded && ( | |||
| {normalHoursExceeded && ( | |||
| <Tooltip | |||
| title={t("Leave hours cannot be more than {{hours}}", { | |||
| hours: LEAVE_DAILY_MAX_HOURS, | |||
| })} | |||
| title={t( | |||
| "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}} (timesheet hours: {{timesheetNormalHours}}, leave hours: {{leaveHours}}). Please use other hours for exceeding hours or decrease the leave hours.", | |||
| { | |||
| timesheetNormalHours, | |||
| leaveHours, | |||
| DAILY_NORMAL_MAX_HOURS, | |||
| }, | |||
| )} | |||
| > | |||
| <Info fontSize="small" /> | |||
| </Tooltip> | |||
| @@ -26,6 +26,12 @@ import FullscreenModal from "../FullscreenModal"; | |||
| import MobileLeaveTable from "../LeaveTable/MobileLeaveTable"; | |||
| import useIsMobile from "@/app/utils/useIsMobile"; | |||
| import { HolidaysResult } from "@/app/api/holidays"; | |||
| import { | |||
| DAILY_NORMAL_MAX_HOURS, | |||
| TIMESHEET_DAILY_MAX_HOURS, | |||
| validateLeaveRecord, | |||
| } from "@/app/api/timesheets/utils"; | |||
| import ErrorAlert from "../ErrorAlert"; | |||
| interface Props { | |||
| isOpen: boolean; | |||
| @@ -75,6 +81,15 @@ const LeaveModal: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<RecordLeaveInput>>( | |||
| async (data) => { | |||
| const errors = validateLeaveRecord(data, timesheetRecords); | |||
| if (errors) { | |||
| Object.keys(errors).forEach((date) => | |||
| formProps.setError(date, { | |||
| message: errors[date], | |||
| }), | |||
| ); | |||
| return; | |||
| } | |||
| const savedRecords = await saveLeave(data, username); | |||
| const today = dayjs(); | |||
| @@ -91,7 +106,7 @@ const LeaveModal: React.FC<Props> = ({ | |||
| formProps.reset(newFormValues); | |||
| onClose(); | |||
| }, | |||
| [formProps, onClose, username], | |||
| [formProps, onClose, timesheetRecords, username], | |||
| ); | |||
| const onCancel = useCallback(() => { | |||
| @@ -108,6 +123,20 @@ const LeaveModal: React.FC<Props> = ({ | |||
| [onCancel], | |||
| ); | |||
| 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(); | |||
| return ( | |||
| @@ -135,6 +164,7 @@ const LeaveModal: React.FC<Props> = ({ | |||
| timesheetRecords={timesheetRecords} | |||
| /> | |||
| </Box> | |||
| {errorComponent} | |||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||
| <Button | |||
| variant="outlined" | |||
| @@ -172,6 +202,7 @@ const LeaveModal: React.FC<Props> = ({ | |||
| companyHolidays={companyHolidays} | |||
| leaveTypes={leaveTypes} | |||
| timesheetRecords={timesheetRecords} | |||
| errorComponent={errorComponent} | |||
| /> | |||
| </Box> | |||
| </FullscreenModal> | |||
| @@ -42,7 +42,8 @@ type LeaveEntryRow = Partial< | |||
| const EntryInputTable: React.FC<Props> = ({ day, leaveTypes }) => { | |||
| const { t } = useTranslation("home"); | |||
| const { getValues, setValue } = useFormContext<RecordLeaveInput>(); | |||
| const { getValues, setValue, clearErrors } = | |||
| useFormContext<RecordLeaveInput>(); | |||
| const currentEntries = getValues(day); | |||
| const [entries, setEntries] = useState<LeaveEntryRow[]>(currentEntries || []); | |||
| @@ -207,7 +208,8 @@ const EntryInputTable: React.FC<Props> = ({ day, leaveTypes }) => { | |||
| remark: e.remark, | |||
| })), | |||
| ]); | |||
| }, [getValues, entries, setValue, day]); | |||
| clearErrors(day); | |||
| }, [getValues, entries, setValue, day, clearErrors]); | |||
| const footer = ( | |||
| <Box display="flex" gap={2} alignItems="center"> | |||
| @@ -38,7 +38,7 @@ const MobileLeaveEntry: React.FC<Props> = ({ | |||
| ); | |||
| }, [leaveTypes]); | |||
| const { watch, setValue } = useFormContext<RecordLeaveInput>(); | |||
| const { watch, setValue, clearErrors } = useFormContext<RecordLeaveInput>(); | |||
| const currentEntries = watch(date); | |||
| // Edit modal | |||
| @@ -57,13 +57,14 @@ const MobileLeaveEntry: React.FC<Props> = ({ | |||
| date, | |||
| currentEntries.filter((entry) => entry.id !== defaultValues.id), | |||
| ); | |||
| clearErrors(date); | |||
| setEditModalOpen(false); | |||
| } | |||
| : undefined, | |||
| }); | |||
| setEditModalOpen(true); | |||
| }, | |||
| [currentEntries, date, setValue], | |||
| [clearErrors, currentEntries, date, setValue], | |||
| ); | |||
| const closeEditModal = useCallback(() => { | |||
| @@ -80,12 +81,13 @@ const MobileLeaveEntry: React.FC<Props> = ({ | |||
| ...(e.id === existingEntry.id ? entry : e), | |||
| })), | |||
| ); | |||
| clearErrors(date); | |||
| } else { | |||
| setValue(date, [...currentEntries, entry]); | |||
| } | |||
| setEditModalOpen(false); | |||
| }, | |||
| [currentEntries, date, setValue], | |||
| [clearErrors, currentEntries, date, setValue], | |||
| ); | |||
| return ( | |||
| @@ -13,12 +13,14 @@ interface Props { | |||
| leaveTypes: LeaveType[]; | |||
| timesheetRecords: RecordTimesheetInput; | |||
| companyHolidays: HolidaysResult[]; | |||
| errorComponent?: React.ReactNode; | |||
| } | |||
| const MobileLeaveTable: React.FC<Props> = ({ | |||
| timesheetRecords, | |||
| leaveTypes, | |||
| companyHolidays, | |||
| errorComponent, | |||
| }) => { | |||
| const { watch } = useFormContext<RecordLeaveInput>(); | |||
| const currentInput = watch(); | |||
| @@ -32,6 +34,7 @@ const MobileLeaveTable: React.FC<Props> = ({ | |||
| timesheetEntries={timesheetRecords} | |||
| EntryComponent={MobileLeaveEntry} | |||
| entryComponentProps={{ leaveTypes, companyHolidays }} | |||
| errorComponent={errorComponent} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -85,9 +85,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<RecordTimesheetInput>>( | |||
| async (data) => { | |||
| const errors = validateTimesheet(data, leaveRecords, companyHolidays, { | |||
| skipTaskValidation: fastEntryEnabled, | |||
| }); | |||
| const errors = validateTimesheet(data, leaveRecords, companyHolidays); | |||
| if (errors) { | |||
| Object.keys(errors).forEach((date) => | |||
| formProps.setError(date, { | |||
| @@ -112,14 +110,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| formProps.reset(newFormValues); | |||
| onClose(); | |||
| }, | |||
| [ | |||
| companyHolidays, | |||
| fastEntryEnabled, | |||
| formProps, | |||
| leaveRecords, | |||
| onClose, | |||
| username, | |||
| ], | |||
| [companyHolidays, formProps, leaveRecords, onClose, username], | |||
| ); | |||
| const onCancel = useCallback(() => { | |||
| @@ -117,9 +117,7 @@ const EntryInputTable: React.FC<Props> = ({ | |||
| "", | |||
| ) as TimeEntryRow; | |||
| const error = validateTimeEntry(row, isHoliday, { | |||
| skipTaskValidation: fastEntryEnabled, | |||
| }); | |||
| const error = validateTimeEntry(row, isHoliday); | |||
| // Test for warnings | |||
| let isPlanned; | |||
| @@ -138,7 +136,7 @@ const EntryInputTable: React.FC<Props> = ({ | |||
| apiRef.current.updateRows([{ id, _error: error, isPlanned }]); | |||
| return !error; | |||
| }, | |||
| [apiRef, day, fastEntryEnabled, isHoliday, milestonesByProject], | |||
| [apiRef, day, isHoliday, milestonesByProject], | |||
| ); | |||
| const handleCancel = useCallback( | |||
| @@ -29,6 +29,7 @@ import { | |||
| import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; | |||
| import { DAILY_NORMAL_MAX_HOURS } from "@/app/api/timesheets/utils"; | |||
| import zip from "lodash/zip"; | |||
| import intersectionBy from "lodash/intersectionBy"; | |||
| export interface FastTimeEntryForm { | |||
| projectIds: TimeEntry["projectId"][]; | |||
| @@ -66,6 +67,9 @@ const getID = () => { | |||
| return ++idOffset; | |||
| }; | |||
| const MISC_TASK_GROUP_ID = 5; | |||
| const FAST_ENTRY_TASK_ID = 40; | |||
| const FastTimeEntryModal: React.FC<Props> = ({ | |||
| onSave, | |||
| open, | |||
| @@ -81,6 +85,16 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| i18n: { language }, | |||
| } = useTranslation("home"); | |||
| const allProjectsWithFastEntry = useMemo(() => { | |||
| return allProjects.filter((p) => | |||
| p.tasks.find((t) => t.id === FAST_ENTRY_TASK_ID), | |||
| ); | |||
| }, [allProjects]); | |||
| const allAssignedProjectsWithFastEntry = useMemo(() => { | |||
| return intersectionBy(assignedProjects, allProjectsWithFastEntry, "id"); | |||
| }, [allProjectsWithFastEntry, assignedProjects]); | |||
| const { register, control, reset, trigger, formState, watch } = | |||
| useForm<FastTimeEntryForm>({ | |||
| defaultValues: { | |||
| @@ -94,8 +108,10 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| const remark = watch("remark"); | |||
| const selectedProjects = useMemo(() => { | |||
| return projectIds.map((id) => allProjects.find((p) => p.id === id)); | |||
| }, [allProjects, projectIds]); | |||
| return projectIds.map((id) => | |||
| allProjectsWithFastEntry.find((p) => p.id === id), | |||
| ); | |||
| }, [allProjectsWithFastEntry, projectIds]); | |||
| const normalHoursArray = distributeQuarters( | |||
| inputHours || 0, | |||
| @@ -116,13 +132,19 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| const valid = await trigger(); | |||
| if (valid) { | |||
| onSave( | |||
| projectsWithHours.map(([project, hour, othour]) => ({ | |||
| id: getID(), | |||
| projectId: project?.id, | |||
| inputHours: hour, | |||
| otHours: othour, | |||
| remark, | |||
| })), | |||
| projectsWithHours.map(([project, hour, othour]) => { | |||
| const projectId = project?.id; | |||
| return { | |||
| id: getID(), | |||
| projectId, | |||
| inputHours: hour, | |||
| otHours: othour, | |||
| taskGroupId: projectId ? MISC_TASK_GROUP_ID : undefined, | |||
| taskId: projectId ? FAST_ENTRY_TASK_ID : undefined, | |||
| remark, | |||
| }; | |||
| }), | |||
| recordDate, | |||
| ); | |||
| reset(); | |||
| @@ -154,8 +176,8 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| <ProjectSelect | |||
| error={Boolean(formState.errors.projectIds)} | |||
| multiple | |||
| allProjects={allProjects} | |||
| assignedProjects={assignedProjects} | |||
| allProjects={allProjectsWithFastEntry} | |||
| assignedProjects={allAssignedProjectsWithFastEntry} | |||
| value={field.value} | |||
| onProjectSelect={(newIds) => { | |||
| field.onChange( | |||
| @@ -172,7 +194,7 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| <FormHelperText> | |||
| {formState.errors.projectIds?.message || | |||
| t( | |||
| "The inputted time will be evenly distributed among the selected projects.", | |||
| 'The inputted time will be evenly distributed among the selected projects. Only projects with the "Management Timesheet Allocation" task can use the fast entry.', | |||
| )} | |||
| </FormHelperText> | |||
| </FormControl> | |||
| @@ -222,7 +244,7 @@ const FastTimeEntryModal: React.FC<Props> = ({ | |||
| {...register("remark", { | |||
| validate: (value) => | |||
| projectIds.every((id) => id) || | |||
| value || | |||
| Boolean(value) || | |||
| t("Required for non-billable tasks"), | |||
| })} | |||
| helperText={ | |||
| @@ -87,7 +87,6 @@ const AutocompleteProjectSelect: React.FC<Props> = ({ | |||
| return option.value === (v ?? ""); | |||
| }) | |||
| : options.find((o) => o.value === value) || options[0]; | |||
| // const currentValue = options.find((o) => o.value === value) || options[0]; | |||
| const onChange = useCallback( | |||
| ( | |||