@@ -0,0 +1,274 @@ | |||||
import React, { useCallback, useMemo, useState } from "react"; | |||||
import { HolidaysResult } from "@/app/api/holidays"; | |||||
import { LeaveType } from "@/app/api/timesheets"; | |||||
import dayGridPlugin from "@fullcalendar/daygrid"; | |||||
import interactionPlugin from "@fullcalendar/interaction"; | |||||
import { Box, useTheme } from "@mui/material"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { | |||||
getHolidayForDate, | |||||
getPublicHolidaysForNYears, | |||||
} from "@/app/utils/holidayUtils"; | |||||
import { | |||||
INPUT_DATE_FORMAT, | |||||
convertDateArrayToString, | |||||
} from "@/app/utils/formatUtil"; | |||||
import StyledFullCalendar from "../StyledFullCalendar"; | |||||
import { ProjectWithTasks } from "@/app/api/projects"; | |||||
import { | |||||
LeaveEntry, | |||||
RecordLeaveInput, | |||||
RecordTimesheetInput, | |||||
saveLeave, | |||||
} from "@/app/api/timesheets/actions"; | |||||
import { Props as LeaveEditModalProps } from "../LeaveTable/LeaveEditModal"; | |||||
import LeaveEditModal from "../LeaveTable/LeaveEditModal"; | |||||
import dayjs from "dayjs"; | |||||
import { checkTotalHours } from "@/app/api/timesheets/utils"; | |||||
import unionBy from "lodash/unionBy"; | |||||
export interface Props { | |||||
leaveTypes: LeaveType[]; | |||||
companyHolidays: HolidaysResult[]; | |||||
allProjects: ProjectWithTasks[]; | |||||
leaveRecords: RecordLeaveInput; | |||||
timesheetRecords: RecordTimesheetInput; | |||||
} | |||||
interface EventClickArg { | |||||
event: { | |||||
start: Date | null; | |||||
startStr: string; | |||||
extendedProps: { | |||||
calendar?: string; | |||||
entry?: LeaveEntry; | |||||
}; | |||||
}; | |||||
} | |||||
const LeaveCalendar: React.FC<Props> = ({ | |||||
companyHolidays, | |||||
allProjects, | |||||
leaveTypes, | |||||
timesheetRecords, | |||||
leaveRecords, | |||||
}) => { | |||||
const { t } = useTranslation(["home", "common"]); | |||||
const theme = useTheme(); | |||||
const projectMap = useMemo(() => { | |||||
return allProjects.reduce<{ | |||||
[id: ProjectWithTasks["id"]]: ProjectWithTasks; | |||||
}>((acc, project) => { | |||||
return { ...acc, [project.id]: project }; | |||||
}, {}); | |||||
}, [allProjects]); | |||||
const leaveMap = useMemo(() => { | |||||
return leaveTypes.reduce<{ [id: LeaveType["id"]]: string }>( | |||||
(acc, leaveType) => ({ ...acc, [leaveType.id]: leaveType.name }), | |||||
{}, | |||||
); | |||||
}, [leaveTypes]); | |||||
const [localLeaveRecords, setLocalLeaveEntries] = useState(leaveRecords); | |||||
// 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: defaultValues ? { ...defaultValues } : undefined, | |||||
recordDate, | |||||
isHoliday, | |||||
onDelete: defaultValues | |||||
? async () => { | |||||
if (!recordDate || !leaveRecords[recordDate]) { | |||||
return; | |||||
} | |||||
const leaveEntriesAtDate = leaveRecords[recordDate]; | |||||
const newLeaveRecords = { | |||||
...leaveRecords, | |||||
[recordDate!]: leaveEntriesAtDate.filter( | |||||
(e) => e.id !== defaultValues.id, | |||||
), | |||||
}; | |||||
const savedLeaveRecords = await saveLeave(newLeaveRecords); | |||||
setLocalLeaveEntries(savedLeaveRecords); | |||||
setLeaveEditModalOpen(false); | |||||
} | |||||
: undefined, | |||||
}); | |||||
setLeaveEditModalOpen(true); | |||||
}, | |||||
[leaveRecords], | |||||
); | |||||
const closeLeaveEditModal = useCallback(() => { | |||||
setLeaveEditModalOpen(false); | |||||
}, []); | |||||
// calendar related | |||||
const holidays = useMemo(() => { | |||||
return [ | |||||
...getPublicHolidaysForNYears(2), | |||||
...companyHolidays.map((h) => ({ | |||||
title: h.name, | |||||
date: convertDateArrayToString(h.date, INPUT_DATE_FORMAT), | |||||
extendedProps: { | |||||
calender: "holiday", | |||||
}, | |||||
})), | |||||
].map((e) => ({ | |||||
...e, | |||||
backgroundColor: theme.palette.error.main, | |||||
borderColor: theme.palette.error.main, | |||||
})); | |||||
}, [companyHolidays, theme.palette.error.main]); | |||||
const leaveEntries = useMemo( | |||||
() => | |||||
Object.keys(localLeaveRecords).flatMap((date, index) => { | |||||
return localLeaveRecords[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, | |||||
}, | |||||
})); | |||||
}), | |||||
[leaveMap, localLeaveRecords, t, theme], | |||||
); | |||||
const timeEntries = useMemo( | |||||
() => | |||||
Object.keys(timesheetRecords).flatMap((date, index) => { | |||||
return timesheetRecords[date].map((entry) => ({ | |||||
id: `${date}-${index}-time-${entry.id}`, | |||||
date, | |||||
title: `${t("{{count}} hour", { | |||||
ns: "common", | |||||
count: (entry.inputHours || 0) + (entry.otHours || 0), | |||||
})} (${ | |||||
entry.projectId | |||||
? projectMap[entry.projectId].code | |||||
: t("Non-billable task") | |||||
})`, | |||||
backgroundColor: theme.palette.info.main, | |||||
borderColor: theme.palette.info.main, | |||||
extendedProps: { | |||||
calendar: "timeEntry", | |||||
entry, | |||||
}, | |||||
})); | |||||
}), | |||||
[projectMap, timesheetRecords, t, theme], | |||||
); | |||||
const handleEventClick = useCallback( | |||||
({ event }: EventClickArg) => { | |||||
const dayJsObj = dayjs(event.startStr); | |||||
const holiday = getHolidayForDate(event.startStr, companyHolidays); | |||||
const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | |||||
if ( | |||||
event.extendedProps.calendar === "leaveEntry" && | |||||
event.extendedProps.entry | |||||
) { | |||||
openLeaveEditModal( | |||||
event.extendedProps.entry as LeaveEntry, | |||||
event.startStr, | |||||
Boolean(isHoliday), | |||||
); | |||||
} | |||||
}, | |||||
[companyHolidays, openLeaveEditModal], | |||||
); | |||||
const handleDateClick = useCallback( | |||||
(e: { dateStr: string; dayEl: HTMLElement }) => { | |||||
const dayJsObj = dayjs(e.dateStr); | |||||
const holiday = getHolidayForDate(e.dateStr, companyHolidays); | |||||
const isHoliday = holiday || dayJsObj.day() === 0 || dayJsObj.day() === 6; | |||||
openLeaveEditModal(undefined, e.dateStr, Boolean(isHoliday)); | |||||
}, | |||||
[companyHolidays, openLeaveEditModal], | |||||
); | |||||
const checkTotalHoursForDate = useCallback( | |||||
(newEntry: LeaveEntry, date?: string) => { | |||||
if (!date) { | |||||
throw Error("Invalid date"); | |||||
} | |||||
const leaves = localLeaveRecords[date] || []; | |||||
const timesheets = timesheetRecords[date] || []; | |||||
const leavesWithNewEntry = unionBy( | |||||
[newEntry as LeaveEntry], | |||||
leaves, | |||||
"id", | |||||
); | |||||
const totalHourError = checkTotalHours(timesheets, leavesWithNewEntry); | |||||
if (totalHourError) throw Error(totalHourError); | |||||
}, | |||||
[localLeaveRecords, timesheetRecords], | |||||
); | |||||
const handleSaveLeave = useCallback( | |||||
async (leaveEntry: LeaveEntry, recordDate?: string) => { | |||||
checkTotalHoursForDate(leaveEntry, recordDate); | |||||
const leaveEntriesAtDate = leaveRecords[recordDate!] || []; | |||||
const newLeaveRecords = { | |||||
...leaveRecords, | |||||
[recordDate!]: [ | |||||
...leaveEntriesAtDate.filter((e) => e.id !== leaveEntry.id), | |||||
leaveEntry, | |||||
], | |||||
}; | |||||
const savedLeaveRecords = await saveLeave(newLeaveRecords); | |||||
setLocalLeaveEntries(savedLeaveRecords); | |||||
setLeaveEditModalOpen(false); | |||||
}, | |||||
[checkTotalHoursForDate, leaveRecords], | |||||
); | |||||
return ( | |||||
<Box> | |||||
<StyledFullCalendar | |||||
plugins={[dayGridPlugin, interactionPlugin]} | |||||
initialView="dayGridMonth" | |||||
buttonText={{ today: t("Today") }} | |||||
events={[...holidays, ...timeEntries, ...leaveEntries]} | |||||
eventClick={handleEventClick} | |||||
dateClick={handleDateClick} | |||||
/> | |||||
<LeaveEditModal | |||||
modalSx={{ maxWidth: 400 }} | |||||
leaveTypes={leaveTypes} | |||||
open={leaveEditModalOpen} | |||||
onClose={closeLeaveEditModal} | |||||
onSave={handleSaveLeave} | |||||
{...leaveEditModalProps} | |||||
/> | |||||
</Box> | |||||
); | |||||
}; | |||||
export default LeaveCalendar; |
@@ -1,46 +1,16 @@ | |||||
import React, { useCallback, useEffect, useMemo } from "react"; | |||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
import React from "react"; | |||||
import FullscreenModal from "../FullscreenModal"; | |||||
import { | import { | ||||
Box, | Box, | ||||
Button, | |||||
Card, | Card, | ||||
CardActions, | |||||
CardContent, | CardContent, | ||||
Modal, | Modal, | ||||
ModalProps, | |||||
SxProps, | SxProps, | ||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { Check, Close } from "@mui/icons-material"; | |||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||||
import { | |||||
RecordLeaveInput, | |||||
RecordTimesheetInput, | |||||
saveLeave, | |||||
} from "@/app/api/timesheets/actions"; | |||||
import dayjs from "dayjs"; | |||||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import LeaveTable from "../LeaveTable"; | |||||
import { LeaveType } from "@/app/api/timesheets"; | |||||
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; | |||||
onClose: () => void; | |||||
defaultLeaveRecords?: RecordLeaveInput; | |||||
leaveTypes: LeaveType[]; | |||||
timesheetRecords: RecordTimesheetInput; | |||||
companyHolidays: HolidaysResult[]; | |||||
} | |||||
import LeaveCalendar, { Props as LeaveCalendarProps } from "./LeaveCalendar"; | |||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
position: "absolute", | position: "absolute", | ||||
@@ -52,167 +22,56 @@ const modalSx: SxProps = { | |||||
maxWidth: 1400, | maxWidth: 1400, | ||||
}; | }; | ||||
interface Props extends LeaveCalendarProps { | |||||
open: boolean; | |||||
onClose: () => void; | |||||
} | |||||
const LeaveModal: React.FC<Props> = ({ | const LeaveModal: React.FC<Props> = ({ | ||||
isOpen, | |||||
open, | |||||
onClose, | onClose, | ||||
defaultLeaveRecords, | |||||
timesheetRecords, | |||||
leaveTypes, | leaveTypes, | ||||
companyHolidays, | companyHolidays, | ||||
allProjects, | |||||
leaveRecords, | |||||
timesheetRecords, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
const isMobile = useIsMobile(); | |||||
const defaultValues = useMemo(() => { | |||||
const today = dayjs(); | |||||
return Array(7) | |||||
.fill(undefined) | |||||
.reduce<RecordLeaveInput>((acc, _, index) => { | |||||
const date = today.subtract(index, "day").format(INPUT_DATE_FORMAT); | |||||
return { | |||||
...acc, | |||||
[date]: defaultLeaveRecords?.[date] ?? [], | |||||
}; | |||||
}, {}); | |||||
}, [defaultLeaveRecords]); | |||||
const formProps = useForm<RecordLeaveInput>({ defaultValues }); | |||||
useEffect(() => { | |||||
formProps.reset(defaultValues); | |||||
}, [defaultValues, formProps]); | |||||
const onSubmit = useCallback<SubmitHandler<RecordLeaveInput>>( | |||||
async (data) => { | |||||
const errors = validateLeaveRecord( | |||||
data, | |||||
timesheetRecords, | |||||
companyHolidays, | |||||
); | |||||
if (errors) { | |||||
Object.keys(errors).forEach((date) => | |||||
formProps.setError(date, { | |||||
message: errors[date], | |||||
}), | |||||
); | |||||
return; | |||||
} | |||||
const savedRecords = await saveLeave(data); | |||||
const today = dayjs(); | |||||
const newFormValues = Array(7) | |||||
.fill(undefined) | |||||
.reduce<RecordLeaveInput>((acc, _, index) => { | |||||
const date = today.subtract(index, "day").format(INPUT_DATE_FORMAT); | |||||
return { | |||||
...acc, | |||||
[date]: savedRecords[date] ?? [], | |||||
}; | |||||
}, {}); | |||||
formProps.reset(newFormValues); | |||||
onClose(); | |||||
}, | |||||
[companyHolidays, formProps, onClose, timesheetRecords], | |||||
); | |||||
const onCancel = useCallback(() => { | |||||
formProps.reset(defaultValues); | |||||
onClose(); | |||||
}, [defaultValues, formProps, onClose]); | |||||
const onModalClose = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
(_, reason) => { | |||||
if (reason !== "backdropClick") { | |||||
onCancel(); | |||||
} | |||||
}, | |||||
[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 title = t("Record leave"); | |||||
const content = ( | |||||
<LeaveCalendar | |||||
leaveTypes={leaveTypes} | |||||
companyHolidays={companyHolidays} | |||||
allProjects={allProjects} | |||||
leaveRecords={leaveRecords} | |||||
timesheetRecords={timesheetRecords} | |||||
/> | /> | ||||
); | ); | ||||
const matches = useIsMobile(); | |||||
return ( | |||||
<FormProvider {...formProps}> | |||||
{!matches ? ( | |||||
// Desktop version | |||||
<Modal open={isOpen} onClose={onModalClose}> | |||||
<Card sx={modalSx}> | |||||
<CardContent | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit)} | |||||
> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Record Leave")} | |||||
</Typography> | |||||
<Box | |||||
sx={{ | |||||
marginInline: -3, | |||||
marginBlock: 4, | |||||
}} | |||||
> | |||||
<LeaveTable | |||||
companyHolidays={companyHolidays} | |||||
leaveTypes={leaveTypes} | |||||
timesheetRecords={timesheetRecords} | |||||
/> | |||||
</Box> | |||||
{errorComponent} | |||||
<CardActions sx={{ justifyContent: "flex-end" }}> | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<Close />} | |||||
onClick={onCancel} | |||||
> | |||||
{t("Cancel")} | |||||
</Button> | |||||
<Button variant="contained" startIcon={<Check />} type="submit"> | |||||
{t("Save")} | |||||
</Button> | |||||
</CardActions> | |||||
</CardContent> | |||||
</Card> | |||||
</Modal> | |||||
) : ( | |||||
// Mobile version | |||||
<FullscreenModal | |||||
open={isOpen} | |||||
onClose={onModalClose} | |||||
closeModal={onCancel} | |||||
> | |||||
<Box | |||||
display="flex" | |||||
flexDirection="column" | |||||
gap={2} | |||||
height="100%" | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit)} | |||||
> | |||||
<Typography variant="h6" padding={2} flex="none"> | |||||
{t("Record Leave")} | |||||
</Typography> | |||||
<MobileLeaveTable | |||||
companyHolidays={companyHolidays} | |||||
leaveTypes={leaveTypes} | |||||
timesheetRecords={timesheetRecords} | |||||
errorComponent={errorComponent} | |||||
/> | |||||
return isMobile ? ( | |||||
<FullscreenModal open={open} onClose={onClose} closeModal={onClose}> | |||||
<Box display="flex" flexDirection="column" gap={2} height="100%"> | |||||
<Typography variant="h6" flex="none" padding={2}> | |||||
{title} | |||||
</Typography> | |||||
<Box paddingInline={2}>{content}</Box> | |||||
</Box> | |||||
</FullscreenModal> | |||||
) : ( | |||||
<Modal open={open} onClose={onClose}> | |||||
<Card sx={modalSx}> | |||||
<CardContent> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{title} | |||||
</Typography> | |||||
<Box maxHeight={900} overflow="scroll"> | |||||
{content} | |||||
</Box> | </Box> | ||||
</FullscreenModal> | |||||
)} | |||||
</FormProvider> | |||||
</CardContent> | |||||
</Card> | |||||
</Modal> | |||||
); | ); | ||||
}; | }; | ||||
@@ -4,7 +4,7 @@ import React, { useCallback, useState } from "react"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
import { CalendarMonth, EditCalendar, MoreTime } from "@mui/icons-material"; | |||||
import { CalendarMonth, EditCalendar, Luggage, MoreTime } from "@mui/icons-material"; | |||||
import { Menu, MenuItem, SxProps, Typography } from "@mui/material"; | import { Menu, MenuItem, SxProps, Typography } from "@mui/material"; | ||||
import AssignedProjects from "./AssignedProjects"; | import AssignedProjects from "./AssignedProjects"; | ||||
import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; | import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; | ||||
@@ -19,6 +19,7 @@ import PastEntryCalendarModal from "../PastEntryCalendar/PastEntryCalendarModal" | |||||
import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
import { TimesheetAmendmentModal } from "../TimesheetAmendment/TimesheetAmendmentModal"; | import { TimesheetAmendmentModal } from "../TimesheetAmendment/TimesheetAmendmentModal"; | ||||
import TimeLeaveModal from "../TimeLeaveModal/TimeLeaveModal"; | import TimeLeaveModal from "../TimeLeaveModal/TimeLeaveModal"; | ||||
import LeaveModal from "../LeaveModal"; | |||||
export interface Props { | export interface Props { | ||||
leaveTypes: LeaveType[]; | leaveTypes: LeaveType[]; | ||||
@@ -55,6 +56,7 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
const [isTimeLeaveModalVisible, setTimeLeaveModalVisible] = useState(false); | const [isTimeLeaveModalVisible, setTimeLeaveModalVisible] = useState(false); | ||||
const [isLeaveCalendarVisible, setLeaveCalendarVisible] = useState(false); | |||||
const [isPastEventModalVisible, setPastEventModalVisible] = useState(false); | const [isPastEventModalVisible, setPastEventModalVisible] = useState(false); | ||||
const [isTimesheetAmendmentVisible, setisTimesheetAmendmentVisible] = | const [isTimesheetAmendmentVisible, setisTimesheetAmendmentVisible] = | ||||
useState(false); | useState(false); | ||||
@@ -81,6 +83,15 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
setTimeLeaveModalVisible(false); | setTimeLeaveModalVisible(false); | ||||
}, []); | }, []); | ||||
const handleOpenLeaveCalendarButton = useCallback(() => { | |||||
setAnchorEl(null); | |||||
setLeaveCalendarVisible(true); | |||||
}, []); | |||||
const handleCloseLeaveCalendarButton = useCallback(() => { | |||||
setLeaveCalendarVisible(false); | |||||
}, []); | |||||
const handlePastEventClick = useCallback(() => { | const handlePastEventClick = useCallback(() => { | ||||
setAnchorEl(null); | setAnchorEl(null); | ||||
setPastEventModalVisible(true); | setPastEventModalVisible(true); | ||||
@@ -136,6 +147,10 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
<MoreTime /> | <MoreTime /> | ||||
{t("Enter Timesheet")} | {t("Enter Timesheet")} | ||||
</MenuItem> | </MenuItem> | ||||
<MenuItem onClick={handleOpenLeaveCalendarButton} sx={menuItemSx}> | |||||
<Luggage /> | |||||
{t("Record Leave")} | |||||
</MenuItem> | |||||
<MenuItem onClick={handlePastEventClick} sx={menuItemSx}> | <MenuItem onClick={handlePastEventClick} sx={menuItemSx}> | ||||
<CalendarMonth /> | <CalendarMonth /> | ||||
{t("View Past Entries")} | {t("View Past Entries")} | ||||
@@ -167,6 +182,15 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
timesheetRecords={defaultTimesheets} | timesheetRecords={defaultTimesheets} | ||||
leaveRecords={defaultLeaveRecords} | leaveRecords={defaultLeaveRecords} | ||||
/> | /> | ||||
<LeaveModal | |||||
open={isLeaveCalendarVisible} | |||||
onClose={handleCloseLeaveCalendarButton} | |||||
leaveTypes={leaveTypes} | |||||
companyHolidays={holidays} | |||||
allProjects={allProjects} | |||||
leaveRecords={defaultLeaveRecords} | |||||
timesheetRecords={defaultTimesheets} | |||||
/> | |||||
{assignedProjects.length > 0 ? ( | {assignedProjects.length > 0 ? ( | ||||
<AssignedProjects | <AssignedProjects | ||||
assignedProjects={assignedProjects} | assignedProjects={assignedProjects} | ||||