| @@ -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} | ||||