Auteur | SHA1 | Bericht | Datum |
---|---|---|---|
|
56799174cc | Allow temporary saving for timesheet entry | 10 maanden geleden |
|
a20b375dce | Optional remarks for miscellaneous tasks | 10 maanden geleden |
@@ -47,7 +47,7 @@ export const validateTimeEntry = ( | |||||
} else { | } else { | ||||
if (entry.taskGroupId && !entry.taskId) { | if (entry.taskGroupId && !entry.taskId) { | ||||
error.taskId = "Required"; | error.taskId = "Required"; | ||||
} else if (!entry.remark) { | |||||
} else if (!entry.taskGroupId && !entry.remark) { | |||||
error.remark = "Required for non-billable tasks"; | error.remark = "Required for non-billable tasks"; | ||||
} | } | ||||
} | } | ||||
@@ -133,7 +133,10 @@ export const validateTimeLeaveRecord = ( | |||||
} | } | ||||
}); | }); | ||||
return Object.keys(errors).length > 0 ? errors : undefined; | |||||
const hasErrors = Object.keys(errors).length > 0; | |||||
const temporarilySaveable = isTemporarilySaveable(records, errors, holidays); | |||||
return !temporarilySaveable && hasErrors ? errors : undefined; | |||||
}; | }; | ||||
export const checkTotalHours = ( | export const checkTotalHours = ( | ||||
@@ -180,3 +183,35 @@ export const checkTotalHours = ( | |||||
export const DAILY_NORMAL_MAX_HOURS = 8; | export const DAILY_NORMAL_MAX_HOURS = 8; | ||||
export const TIMESHEET_DAILY_MAX_HOURS = 20; | export const TIMESHEET_DAILY_MAX_HOURS = 20; | ||||
export const isTemporarilySaveable = ( | |||||
records: RecordTimeLeaveInput, | |||||
errors: { [date: string]: string }, | |||||
holidays: Set<string>, | |||||
): boolean => { | |||||
const filledDates = Object.keys(records) | |||||
.reduce<{ date: string; hasFilled: boolean }[]>((acc, date) => { | |||||
const dayJsObj = dayjs(date); | |||||
const isHoliday = | |||||
holidays.has(date) || dayJsObj.day() === 0 || dayJsObj.day() === 6; | |||||
if (isHoliday) { | |||||
return acc; | |||||
} | |||||
return [...acc, { date, hasFilled: !Boolean(errors[date]) }]; | |||||
}, []) | |||||
.sort((a, b) => dayjs(a.date).diff(dayjs(b.date))); | |||||
const isConsecutivelyFilled = filledDates.every((currentDate, index) => { | |||||
if (index === 0) { | |||||
return true; | |||||
} | |||||
if (currentDate.hasFilled && !filledDates[index - 1].hasFilled) { | |||||
return false; | |||||
} | |||||
return true; | |||||
}); | |||||
return isConsecutivelyFilled; | |||||
}; |
@@ -33,6 +33,7 @@ interface Props<EntryComponentProps = object> { | |||||
>; | >; | ||||
entryComponentProps: EntryComponentProps; | entryComponentProps: EntryComponentProps; | ||||
errorComponent?: React.ReactNode; | errorComponent?: React.ReactNode; | ||||
onSubmit?: () => void; | |||||
} | } | ||||
function DateHoursList<EntryTableProps>({ | function DateHoursList<EntryTableProps>({ | ||||
@@ -43,6 +44,7 @@ function DateHoursList<EntryTableProps>({ | |||||
entryComponentProps, | entryComponentProps, | ||||
companyHolidays, | companyHolidays, | ||||
errorComponent, | errorComponent, | ||||
onSubmit, | |||||
}: Props<EntryTableProps>) { | }: Props<EntryTableProps>) { | ||||
const { | const { | ||||
t, | t, | ||||
@@ -240,7 +242,12 @@ function DateHoursList<EntryTableProps>({ | |||||
{t("Done")} | {t("Done")} | ||||
</Button> | </Button> | ||||
) : ( | ) : ( | ||||
<Button variant="contained" startIcon={<Check />} type="submit"> | |||||
<Button | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
onClick={onSubmit} | |||||
> | |||||
{t("Save")} | {t("Save")} | ||||
</Button> | </Button> | ||||
)} | )} | ||||
@@ -237,7 +237,14 @@ const TimeLeaveModal: React.FC<Props> = ({ | |||||
> | > | ||||
{t("Cancel")} | {t("Cancel")} | ||||
</Button> | </Button> | ||||
<Button variant="contained" startIcon={<Check />} type="submit"> | |||||
<Button | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
onClick={() => { | |||||
formProps.clearErrors(); | |||||
}} | |||||
> | |||||
{t("Save")} | {t("Save")} | ||||
</Button> | </Button> | ||||
</CardActions> | </CardActions> | ||||
@@ -277,6 +284,9 @@ const TimeLeaveModal: React.FC<Props> = ({ | |||||
miscTasks, | miscTasks, | ||||
}} | }} | ||||
errorComponent={errorComponent} | errorComponent={errorComponent} | ||||
onSubmit={() => { | |||||
formProps.clearErrors(); | |||||
}} | |||||
/> | /> | ||||
</Box> | </Box> | ||||
</FullscreenModal> | </FullscreenModal> | ||||
@@ -304,7 +304,7 @@ const TimesheetEditModal: React.FC<Props> = ({ | |||||
error={Boolean(formState.errors.remark)} | error={Boolean(formState.errors.remark)} | ||||
{...register("remark", { | {...register("remark", { | ||||
validate: (value) => | validate: (value) => | ||||
Boolean(projectId || value) || | |||||
Boolean(projectId || taskGroupId || value) || | |||||
t("Required for non-billable tasks"), | t("Required for non-billable tasks"), | ||||
})} | })} | ||||
helperText={formState.errors.remark?.message} | helperText={formState.errors.remark?.message} | ||||