@@ -4,6 +4,7 @@ import UserWorkspacePage from "@/components/UserWorkspacePage"; | |||||
import { | import { | ||||
fetchLeaveTypes, | fetchLeaveTypes, | ||||
fetchLeaves, | fetchLeaves, | ||||
fetchTeamMemberLeaves, | |||||
fetchTeamMemberTimesheets, | fetchTeamMemberTimesheets, | ||||
fetchTimesheets, | fetchTimesheets, | ||||
} from "@/app/api/timesheets"; | } from "@/app/api/timesheets"; | ||||
@@ -31,6 +32,7 @@ const Home: React.FC = async () => { | |||||
fetchProjectWithTasks(); | fetchProjectWithTasks(); | ||||
fetchHolidays(); | fetchHolidays(); | ||||
fetchTeamMemberTimesheets(username); | fetchTeamMemberTimesheets(username); | ||||
fetchTeamMemberLeaves(username); | |||||
return ( | return ( | ||||
<I18nProvider namespaces={["home", "common"]}> | <I18nProvider namespaces={["home", "common"]}> | ||||
@@ -79,6 +79,21 @@ export const saveMemberEntry = async (data: { | |||||
); | ); | ||||
}; | }; | ||||
export const saveMemberLeave = async (data: { | |||||
staffId: number; | |||||
entry: LeaveEntry; | |||||
recordDate?: string; | |||||
}) => { | |||||
return serverFetchJson<RecordLeaveInput>( | |||||
`${BASE_API_URL}/timesheets/saveMemberLeave`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
}; | |||||
export const revalidateCacheAfterAmendment = () => { | export const revalidateCacheAfterAmendment = () => { | ||||
revalidatePath("/(main)/home"); | revalidatePath("/(main)/home"); | ||||
}; | }; |
@@ -16,6 +16,14 @@ export type TeamTimeSheets = { | |||||
}; | }; | ||||
}; | }; | ||||
export type TeamLeaves = { | |||||
[memberId: number]: { | |||||
leaveEntries: RecordLeaveInput; | |||||
staffId: string; | |||||
name: string; | |||||
}; | |||||
}; | |||||
export const fetchTimesheets = cache(async (username: string) => { | export const fetchTimesheets = cache(async (username: string) => { | ||||
return serverFetchJson<RecordTimesheetInput>(`${BASE_API_URL}/timesheets`, { | return serverFetchJson<RecordTimesheetInput>(`${BASE_API_URL}/timesheets`, { | ||||
next: { tags: [`timesheets_${username}`] }, | next: { tags: [`timesheets_${username}`] }, | ||||
@@ -45,3 +53,9 @@ export const fetchTeamMemberTimesheets = cache(async (username: string) => { | |||||
}, | }, | ||||
); | ); | ||||
}); | }); | ||||
export const fetchTeamMemberLeaves = cache(async (username: string) => { | |||||
return serverFetchJson<TeamLeaves>(`${BASE_API_URL}/timesheets/teamLeaves`, { | |||||
next: { tags: [`team_leaves_${username}`] }, | |||||
}); | |||||
}); |
@@ -1,6 +1,7 @@ | |||||
import { LeaveType } from "@/app/api/timesheets"; | import { LeaveType } from "@/app/api/timesheets"; | ||||
import { LeaveEntry } from "@/app/api/timesheets/actions"; | import { LeaveEntry } from "@/app/api/timesheets/actions"; | ||||
import { LEAVE_DAILY_MAX_HOURS } from "@/app/api/timesheets/utils"; | import { LEAVE_DAILY_MAX_HOURS } from "@/app/api/timesheets/utils"; | ||||
import { shortDateFormatter } from "@/app/utils/formatUtil"; | |||||
import { roundToNearestQuarter } from "@/app/utils/manhourUtils"; | import { roundToNearestQuarter } from "@/app/utils/manhourUtils"; | ||||
import { Check, Delete } from "@mui/icons-material"; | import { Check, Delete } from "@mui/icons-material"; | ||||
import { | import { | ||||
@@ -15,16 +16,20 @@ import { | |||||
Select, | Select, | ||||
SxProps, | SxProps, | ||||
TextField, | TextField, | ||||
Typography, | |||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import React, { useCallback, useEffect } from "react"; | import React, { useCallback, useEffect } from "react"; | ||||
import { Controller, useForm } from "react-hook-form"; | import { Controller, useForm } from "react-hook-form"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
export interface Props extends Omit<ModalProps, "children"> { | export interface Props extends Omit<ModalProps, "children"> { | ||||
onSave: (leaveEntry: LeaveEntry) => void; | |||||
onSave: (leaveEntry: LeaveEntry, recordDate?: string) => Promise<void>; | |||||
onDelete?: () => void; | onDelete?: () => void; | ||||
leaveTypes: LeaveType[]; | leaveTypes: LeaveType[]; | ||||
defaultValues?: Partial<LeaveEntry>; | defaultValues?: Partial<LeaveEntry>; | ||||
modalSx?: SxProps; | |||||
recordDate?: string; | |||||
isHoliday?: boolean; | |||||
} | } | ||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
@@ -46,8 +51,14 @@ const LeaveEditModal: React.FC<Props> = ({ | |||||
onClose, | onClose, | ||||
leaveTypes, | leaveTypes, | ||||
defaultValues, | defaultValues, | ||||
recordDate, | |||||
modalSx: mSx, | |||||
isHoliday, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation("home"); | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation("home"); | |||||
const { register, control, reset, getValues, trigger, formState } = | const { register, control, reset, getValues, trigger, formState } = | ||||
useForm<LeaveEntry>({ | useForm<LeaveEntry>({ | ||||
defaultValues: { | defaultValues: { | ||||
@@ -62,10 +73,10 @@ const LeaveEditModal: React.FC<Props> = ({ | |||||
const saveHandler = useCallback(async () => { | const saveHandler = useCallback(async () => { | ||||
const valid = await trigger(); | const valid = await trigger(); | ||||
if (valid) { | if (valid) { | ||||
onSave(getValues()); | |||||
await onSave(getValues(), recordDate); | |||||
reset({ id: Date.now() }); | reset({ id: Date.now() }); | ||||
} | } | ||||
}, [getValues, onSave, reset, trigger]); | |||||
}, [getValues, onSave, recordDate, reset, trigger]); | |||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
(...args) => { | (...args) => { | ||||
@@ -77,7 +88,16 @@ const LeaveEditModal: React.FC<Props> = ({ | |||||
return ( | return ( | ||||
<Modal open={open} onClose={closeHandler}> | <Modal open={open} onClose={closeHandler}> | ||||
<Paper sx={modalSx}> | |||||
<Paper sx={{ ...modalSx, ...mSx }}> | |||||
{recordDate && ( | |||||
<Typography | |||||
variant="h6" | |||||
marginBlockEnd={2} | |||||
color={isHoliday ? "error.main" : undefined} | |||||
> | |||||
{shortDateFormatter(language).format(new Date(recordDate))} | |||||
</Typography> | |||||
)} | |||||
<FormControl fullWidth> | <FormControl fullWidth> | ||||
<InputLabel>{t("Leave Type")}</InputLabel> | <InputLabel>{t("Leave Type")}</InputLabel> | ||||
<Controller | <Controller | ||||
@@ -72,7 +72,7 @@ const MobileLeaveEntry: React.FC<Props> = ({ | |||||
}, []); | }, []); | ||||
const onSaveEntry = useCallback( | const onSaveEntry = useCallback( | ||||
(entry: LeaveEntry) => { | |||||
async (entry: LeaveEntry) => { | |||||
const existingEntry = currentEntries.find((e) => e.id === entry.id); | const existingEntry = currentEntries.find((e) => e.id === entry.id); | ||||
if (existingEntry) { | if (existingEntry) { | ||||
setValue( | setValue( | ||||
@@ -1,31 +1,44 @@ | |||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
import { TeamTimeSheets } from "@/app/api/timesheets"; | |||||
import { LeaveType, TeamLeaves, TeamTimeSheets } from "@/app/api/timesheets"; | |||||
import dayGridPlugin from "@fullcalendar/daygrid"; | import dayGridPlugin from "@fullcalendar/daygrid"; | ||||
import interactionPlugin from "@fullcalendar/interaction"; | import interactionPlugin from "@fullcalendar/interaction"; | ||||
import { Autocomplete, Stack, TextField, useTheme } from "@mui/material"; | import { Autocomplete, Stack, TextField, useTheme } from "@mui/material"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import transform from "lodash/transform"; | import transform from "lodash/transform"; | ||||
import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||||
import { | |||||
getHolidayForDate, | |||||
getPublicHolidaysForNYears, | |||||
} from "@/app/utils/holidayUtils"; | |||||
import { | import { | ||||
INPUT_DATE_FORMAT, | INPUT_DATE_FORMAT, | ||||
convertDateArrayToString, | convertDateArrayToString, | ||||
} from "@/app/utils/formatUtil"; | } from "@/app/utils/formatUtil"; | ||||
import StyledFullCalendar from "../StyledFullCalendar"; | import StyledFullCalendar from "../StyledFullCalendar"; | ||||
import { ProjectWithTasks } from "@/app/api/projects"; | import { ProjectWithTasks } from "@/app/api/projects"; | ||||
import { TimeEntry, saveMemberEntry } from "@/app/api/timesheets/actions"; | |||||
import { | |||||
LeaveEntry, | |||||
TimeEntry, | |||||
saveMemberEntry, | |||||
saveMemberLeave, | |||||
} from "@/app/api/timesheets/actions"; | |||||
import TimesheetEditModal, { | import TimesheetEditModal, { | ||||
Props as TimesheetEditModalProps, | Props as TimesheetEditModalProps, | ||||
} from "../TimesheetTable/TimesheetEditModal"; | } from "../TimesheetTable/TimesheetEditModal"; | ||||
import { Props as LeaveEditModalProps } from "../LeaveTable/LeaveEditModal"; | |||||
import LeaveEditModal from "../LeaveTable/LeaveEditModal"; | |||||
import dayjs from "dayjs"; | |||||
export interface Props { | export interface Props { | ||||
leaveTypes: LeaveType[]; | |||||
teamLeaves: TeamLeaves; | |||||
teamTimesheets: TeamTimeSheets; | teamTimesheets: TeamTimeSheets; | ||||
companyHolidays: HolidaysResult[]; | companyHolidays: HolidaysResult[]; | ||||
allProjects: ProjectWithTasks[]; | allProjects: ProjectWithTasks[]; | ||||
} | } | ||||
type MemberOption = TeamTimeSheets[0] & { id: string }; | |||||
type MemberOption = TeamTimeSheets[0] & TeamLeaves[0] & { id: string }; | |||||
interface EventClickArg { | interface EventClickArg { | ||||
event: { | event: { | ||||
@@ -33,7 +46,7 @@ interface EventClickArg { | |||||
startStr: string; | startStr: string; | ||||
extendedProps: { | extendedProps: { | ||||
calendar?: string; | calendar?: string; | ||||
entry?: TimeEntry; | |||||
entry?: TimeEntry | LeaveEntry; | |||||
memberId?: string; | memberId?: string; | ||||
}; | }; | ||||
}; | }; | ||||
@@ -41,8 +54,10 @@ interface EventClickArg { | |||||
const TimesheetAmendment: React.FC<Props> = ({ | const TimesheetAmendment: React.FC<Props> = ({ | ||||
teamTimesheets, | teamTimesheets, | ||||
teamLeaves, | |||||
companyHolidays, | companyHolidays, | ||||
allProjects, | allProjects, | ||||
leaveTypes, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation(["home", "common"]); | const { t } = useTranslation(["home", "common"]); | ||||
@@ -56,23 +71,34 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
}, {}); | }, {}); | ||||
}, [allProjects]); | }, [allProjects]); | ||||
const leaveMap = useMemo(() => { | |||||
return leaveTypes.reduce<{ [id: LeaveType["id"]]: string }>( | |||||
(acc, leaveType) => ({ ...acc, [leaveType.id]: leaveType.name }), | |||||
{}, | |||||
); | |||||
}, [leaveTypes]); | |||||
// Use a local state to manage updates after a mutation | // Use a local state to manage updates after a mutation | ||||
const [localTeamTimesheets, setLocalTeamTimesheets] = | const [localTeamTimesheets, setLocalTeamTimesheets] = | ||||
useState(teamTimesheets); | useState(teamTimesheets); | ||||
const [localTeamLeaves, setLocalTeamLeaves] = useState(teamLeaves); | |||||
// member select | // member select | ||||
const allMembers = useMemo(() => { | const allMembers = useMemo(() => { | ||||
return transform<TeamTimeSheets[0], MemberOption[]>( | return transform<TeamTimeSheets[0], MemberOption[]>( | ||||
localTeamTimesheets, | localTeamTimesheets, | ||||
(acc, memberTimesheet, id) => { | (acc, memberTimesheet, id) => { | ||||
const leaves = localTeamLeaves[parseInt(id)]; | |||||
return acc.push({ | return acc.push({ | ||||
...leaves, | |||||
...memberTimesheet, | ...memberTimesheet, | ||||
id, | id, | ||||
}); | }); | ||||
}, | }, | ||||
[], | [], | ||||
); | ); | ||||
}, [localTeamTimesheets]); | |||||
}, [localTeamLeaves, localTeamTimesheets]); | |||||
const [selectedStaff, setSelectedStaff] = useState<MemberOption>( | const [selectedStaff, setSelectedStaff] = useState<MemberOption>( | ||||
allMembers[0], | allMembers[0], | ||||
); | ); | ||||
@@ -91,10 +117,11 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
const [editModalOpen, setEditModalOpen] = useState(false); | const [editModalOpen, setEditModalOpen] = useState(false); | ||||
const openEditModal = useCallback( | const openEditModal = useCallback( | ||||
(defaultValues?: TimeEntry, recordDate?: string) => { | |||||
(defaultValues?: TimeEntry, recordDate?: string, isHoliday?: boolean) => { | |||||
setEditModalProps({ | setEditModalProps({ | ||||
defaultValues, | defaultValues, | ||||
recordDate, | recordDate, | ||||
isHoliday, | |||||
}); | }); | ||||
setEditModalOpen(true); | setEditModalOpen(true); | ||||
}, | }, | ||||
@@ -105,6 +132,28 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
setEditModalOpen(false); | setEditModalOpen(false); | ||||
}, []); | }, []); | ||||
// leave edit modal related | |||||
const [leaveEditModalProps, setLeaveEditModalProps] = useState< | |||||
Partial<LeaveEditModalProps> | |||||
>({}); | |||||
const [leaveEditModalOpen, setLeaveEditModalOpen] = useState(false); | |||||
const openLeaveEditModal = useCallback( | |||||
(defaultValues?: LeaveEntry, recordDate?: string, isHoliday?: boolean) => { | |||||
setLeaveEditModalProps({ | |||||
defaultValues, | |||||
recordDate, | |||||
isHoliday, | |||||
}); | |||||
setLeaveEditModalOpen(true); | |||||
}, | |||||
[], | |||||
); | |||||
const closeLeaveEditModal = useCallback(() => { | |||||
setLeaveEditModalOpen(false); | |||||
}, []); | |||||
// calendar related | // calendar related | ||||
const holidays = useMemo(() => { | const holidays = useMemo(() => { | ||||
return [ | return [ | ||||
@@ -123,11 +172,34 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
})); | })); | ||||
}, [companyHolidays, theme.palette.error.main]); | }, [companyHolidays, theme.palette.error.main]); | ||||
const leaveEntries = useMemo( | |||||
() => | |||||
Object.keys(selectedStaff.leaveEntries).flatMap((date, index) => { | |||||
return selectedStaff.leaveEntries[date].map((entry) => ({ | |||||
id: `${date}-${index}-leave-${entry.id}`, | |||||
date, | |||||
title: `${t("{{count}} hour", { | |||||
ns: "common", | |||||
count: entry.inputHours || 0, | |||||
})} (${leaveMap[entry.leaveTypeId]})`, | |||||
backgroundColor: theme.palette.warning.light, | |||||
borderColor: theme.palette.warning.light, | |||||
textColor: theme.palette.text.primary, | |||||
extendedProps: { | |||||
calendar: "leaveEntry", | |||||
entry, | |||||
memberId: selectedStaff.id, | |||||
}, | |||||
})); | |||||
}), | |||||
[leaveMap, selectedStaff, t, theme], | |||||
); | |||||
const timeEntries = useMemo( | const timeEntries = useMemo( | ||||
() => | () => | ||||
Object.keys(selectedStaff.timeEntries).flatMap((date, index) => { | Object.keys(selectedStaff.timeEntries).flatMap((date, index) => { | ||||
return selectedStaff.timeEntries[date].map((entry) => ({ | return selectedStaff.timeEntries[date].map((entry) => ({ | ||||
id: `${date}-${index}-entry-${entry.id}`, | |||||
id: `${date}-${index}-time-${entry.id}`, | |||||
date, | date, | ||||
title: `${t("{{count}} hour", { | title: `${t("{{count}} hour", { | ||||
ns: "common", | ns: "common", | ||||
@@ -151,21 +223,41 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
const handleEventClick = useCallback( | const handleEventClick = useCallback( | ||||
({ event }: EventClickArg) => { | ({ event }: EventClickArg) => { | ||||
const dayJsObj = dayjs(event.startStr); | |||||
const holiday = getHolidayForDate(event.startStr, companyHolidays); | |||||
const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | |||||
if ( | if ( | ||||
event.extendedProps.calendar === "timeEntry" && | event.extendedProps.calendar === "timeEntry" && | ||||
event.extendedProps.entry | event.extendedProps.entry | ||||
) { | ) { | ||||
openEditModal(event.extendedProps.entry, event.startStr); | |||||
openEditModal( | |||||
event.extendedProps.entry as TimeEntry, | |||||
event.startStr, | |||||
Boolean(isHoliday), | |||||
); | |||||
} else if ( | |||||
event.extendedProps.calendar === "leaveEntry" && | |||||
event.extendedProps.entry | |||||
) { | |||||
openLeaveEditModal( | |||||
event.extendedProps.entry as LeaveEntry, | |||||
event.startStr, | |||||
Boolean(isHoliday), | |||||
); | |||||
} | } | ||||
}, | }, | ||||
[openEditModal], | |||||
[companyHolidays, openEditModal, openLeaveEditModal], | |||||
); | ); | ||||
const handleDateClick = useCallback( | const handleDateClick = useCallback( | ||||
(e: { dateStr: string }) => { | (e: { dateStr: string }) => { | ||||
openEditModal(undefined, e.dateStr); | |||||
const dayJsObj = dayjs(e.dateStr); | |||||
const holiday = getHolidayForDate(e.dateStr, companyHolidays); | |||||
const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | |||||
openEditModal(undefined, e.dateStr, Boolean(isHoliday)); | |||||
}, | }, | ||||
[openEditModal], | |||||
[companyHolidays, openEditModal], | |||||
); | ); | ||||
const handleSave = useCallback( | const handleSave = useCallback( | ||||
@@ -189,6 +281,26 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
[selectedStaff.id], | [selectedStaff.id], | ||||
); | ); | ||||
const handleSaveLeave = useCallback( | |||||
async (leaveEntry: LeaveEntry, recordDate?: string) => { | |||||
const intStaffId = parseInt(selectedStaff.id); | |||||
const newMemberLeaves = await saveMemberLeave({ | |||||
staffId: intStaffId, | |||||
recordDate, | |||||
entry: leaveEntry, | |||||
}); | |||||
setLocalTeamLeaves((leaves) => ({ | |||||
...leaves, | |||||
[intStaffId]: { | |||||
...leaves[intStaffId], | |||||
leaveEntries: newMemberLeaves, | |||||
}, | |||||
})); | |||||
setLeaveEditModalOpen(false); | |||||
}, | |||||
[selectedStaff.id], | |||||
); | |||||
return ( | return ( | ||||
<Stack spacing={2}> | <Stack spacing={2}> | ||||
<Autocomplete | <Autocomplete | ||||
@@ -207,7 +319,7 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
plugins={[dayGridPlugin, interactionPlugin]} | plugins={[dayGridPlugin, interactionPlugin]} | ||||
initialView="dayGridMonth" | initialView="dayGridMonth" | ||||
buttonText={{ today: t("Today") }} | buttonText={{ today: t("Today") }} | ||||
events={[...holidays, ...timeEntries]} | |||||
events={[...holidays, ...timeEntries, ...leaveEntries]} | |||||
eventClick={handleEventClick} | eventClick={handleEventClick} | ||||
dateClick={handleDateClick} | dateClick={handleDateClick} | ||||
/> | /> | ||||
@@ -220,6 +332,14 @@ const TimesheetAmendment: React.FC<Props> = ({ | |||||
onSave={handleSave} | onSave={handleSave} | ||||
{...editModalProps} | {...editModalProps} | ||||
/> | /> | ||||
<LeaveEditModal | |||||
modalSx={{ maxWidth: 400 }} | |||||
leaveTypes={leaveTypes} | |||||
open={leaveEditModalOpen} | |||||
onClose={closeLeaveEditModal} | |||||
onSave={handleSaveLeave} | |||||
{...leaveEditModalProps} | |||||
/> | |||||
</Stack> | </Stack> | ||||
); | ); | ||||
}; | }; | ||||
@@ -32,6 +32,8 @@ interface Props extends TimesheetAmendmentProps { | |||||
export const TimesheetAmendmentModal: React.FC<Props> = ({ | export const TimesheetAmendmentModal: React.FC<Props> = ({ | ||||
open, | open, | ||||
onClose, | onClose, | ||||
leaveTypes, | |||||
teamLeaves, | |||||
teamTimesheets, | teamTimesheets, | ||||
companyHolidays, | companyHolidays, | ||||
allProjects, | allProjects, | ||||
@@ -42,6 +44,8 @@ export const TimesheetAmendmentModal: React.FC<Props> = ({ | |||||
const title = t("Timesheet Amendment"); | const title = t("Timesheet Amendment"); | ||||
const content = ( | const content = ( | ||||
<TimesheetAmendment | <TimesheetAmendment | ||||
leaveTypes={leaveTypes} | |||||
teamLeaves={teamLeaves} | |||||
companyHolidays={companyHolidays} | companyHolidays={companyHolidays} | ||||
teamTimesheets={teamTimesheets} | teamTimesheets={teamTimesheets} | ||||
allProjects={allProjects} | allProjects={allProjects} | ||||
@@ -105,7 +105,7 @@ const TimesheetEditModal: React.FC<Props> = ({ | |||||
const saveHandler = useCallback(async () => { | const saveHandler = useCallback(async () => { | ||||
const valid = await trigger(); | const valid = await trigger(); | ||||
if (valid) { | if (valid) { | ||||
onSave(getValues(), recordDate); | |||||
await onSave(getValues(), recordDate); | |||||
reset({ id: Date.now() }); | reset({ id: Date.now() }); | ||||
} | } | ||||
}, [getValues, onSave, recordDate, reset, trigger]); | }, [getValues, onSave, recordDate, reset, trigger]); | ||||
@@ -126,7 +126,11 @@ const TimesheetEditModal: React.FC<Props> = ({ | |||||
<Modal open={open} onClose={closeHandler}> | <Modal open={open} onClose={closeHandler}> | ||||
<Paper sx={{ ...modalSx, ...mSx }}> | <Paper sx={{ ...modalSx, ...mSx }}> | ||||
{recordDate && ( | {recordDate && ( | ||||
<Typography variant="h6" marginBlockEnd={2}> | |||||
<Typography | |||||
variant="h6" | |||||
marginBlockEnd={2} | |||||
color={isHoliday ? "error.main" : undefined} | |||||
> | |||||
{shortDateFormatter(language).format(new Date(recordDate))} | {shortDateFormatter(language).format(new Date(recordDate))} | ||||
</Typography> | </Typography> | ||||
)} | )} | ||||
@@ -20,7 +20,7 @@ import { | |||||
revalidateCacheAfterAmendment, | revalidateCacheAfterAmendment, | ||||
} from "@/app/api/timesheets/actions"; | } from "@/app/api/timesheets/actions"; | ||||
import LeaveModal from "../LeaveModal"; | import LeaveModal from "../LeaveModal"; | ||||
import { LeaveType, TeamTimeSheets } from "@/app/api/timesheets"; | |||||
import { LeaveType, TeamLeaves, TeamTimeSheets } from "@/app/api/timesheets"; | |||||
import { CalendarIcon } from "@mui/x-date-pickers"; | import { CalendarIcon } from "@mui/x-date-pickers"; | ||||
import PastEntryCalendarModal from "../PastEntryCalendar/PastEntryCalendarModal"; | import PastEntryCalendarModal from "../PastEntryCalendar/PastEntryCalendarModal"; | ||||
import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
@@ -35,6 +35,7 @@ export interface Props { | |||||
defaultTimesheets: RecordTimesheetInput; | defaultTimesheets: RecordTimesheetInput; | ||||
holidays: HolidaysResult[]; | holidays: HolidaysResult[]; | ||||
teamTimesheets: TeamTimeSheets; | teamTimesheets: TeamTimeSheets; | ||||
teamLeaves: TeamLeaves; | |||||
fastEntryEnabled?: boolean; | fastEntryEnabled?: boolean; | ||||
} | } | ||||
@@ -52,6 +53,7 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
defaultTimesheets, | defaultTimesheets, | ||||
holidays, | holidays, | ||||
teamTimesheets, | teamTimesheets, | ||||
teamLeaves, | |||||
fastEntryEnabled, | fastEntryEnabled, | ||||
}) => { | }) => { | ||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
@@ -201,7 +203,9 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
{showTimesheetAmendment && ( | {showTimesheetAmendment && ( | ||||
<TimesheetAmendmentModal | <TimesheetAmendmentModal | ||||
allProjects={allProjects} | allProjects={allProjects} | ||||
leaveTypes={leaveTypes} | |||||
companyHolidays={holidays} | companyHolidays={holidays} | ||||
teamLeaves={teamLeaves} | |||||
teamTimesheets={teamTimesheets} | teamTimesheets={teamTimesheets} | ||||
open={isTimesheetAmendmentVisible} | open={isTimesheetAmendmentVisible} | ||||
onClose={handleAmendmentClose} | onClose={handleAmendmentClose} | ||||
@@ -6,6 +6,7 @@ import UserWorkspacePage from "./UserWorkspacePage"; | |||||
import { | import { | ||||
fetchLeaveTypes, | fetchLeaveTypes, | ||||
fetchLeaves, | fetchLeaves, | ||||
fetchTeamMemberLeaves, | |||||
fetchTeamMemberTimesheets, | fetchTeamMemberTimesheets, | ||||
fetchTimesheets, | fetchTimesheets, | ||||
} from "@/app/api/timesheets"; | } from "@/app/api/timesheets"; | ||||
@@ -17,6 +18,7 @@ interface Props { | |||||
const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | ||||
const [ | const [ | ||||
teamLeaves, | |||||
teamTimesheets, | teamTimesheets, | ||||
assignedProjects, | assignedProjects, | ||||
allProjects, | allProjects, | ||||
@@ -25,6 +27,7 @@ const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | |||||
leaveTypes, | leaveTypes, | ||||
holidays, | holidays, | ||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
fetchTeamMemberLeaves(username), | |||||
fetchTeamMemberTimesheets(username), | fetchTeamMemberTimesheets(username), | ||||
fetchAssignedProjects(username), | fetchAssignedProjects(username), | ||||
fetchProjectWithTasks(), | fetchProjectWithTasks(), | ||||
@@ -36,6 +39,7 @@ const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | |||||
return ( | return ( | ||||
<UserWorkspacePage | <UserWorkspacePage | ||||
teamLeaves={teamLeaves} | |||||
teamTimesheets={teamTimesheets} | teamTimesheets={teamTimesheets} | ||||
allProjects={allProjects} | allProjects={allProjects} | ||||
assignedProjects={assignedProjects} | assignedProjects={assignedProjects} | ||||