@@ -12,6 +12,7 @@ import { | |||||
fetchAssignedProjects, | fetchAssignedProjects, | ||||
fetchProjectWithTasks, | fetchProjectWithTasks, | ||||
} from "@/app/api/projects"; | } from "@/app/api/projects"; | ||||
import { fetchHolidays } from "@/app/api/holidays"; | |||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "User Workspace", | title: "User Workspace", | ||||
@@ -27,6 +28,7 @@ const Home: React.FC = async () => { | |||||
fetchLeaves(username); | fetchLeaves(username); | ||||
fetchLeaveTypes(); | fetchLeaveTypes(); | ||||
fetchProjectWithTasks(); | fetchProjectWithTasks(); | ||||
fetchHolidays(); | |||||
return ( | return ( | ||||
<I18nProvider namespaces={["home"]}> | <I18nProvider namespaces={["home"]}> | ||||
@@ -0,0 +1,49 @@ | |||||
import Holidays from "date-holidays"; | |||||
const hd = new Holidays("HK"); | |||||
export const getPublicHolidaysForNYears = (years: number = 1) => { | |||||
return Array(years) | |||||
.fill(undefined) | |||||
.flatMap((_, index) => { | |||||
const currentYear = new Date().getFullYear(); | |||||
const holidays = hd.getHolidays(currentYear + index); | |||||
return holidays.map((ele) => { | |||||
const tempDay = new Date(ele.date); | |||||
const tempYear = tempDay.getFullYear(); | |||||
const tempMonth = | |||||
tempDay.getMonth() + 1 < 10 | |||||
? `0${tempDay.getMonth() + 1}` | |||||
: tempDay.getMonth() + 1; | |||||
const tempDate = | |||||
tempDay.getDate() < 10 ? `0${tempDay.getDate()}` : tempDay.getDate(); | |||||
let tempName = ""; | |||||
switch (ele.name) { | |||||
case "复活节": | |||||
tempName = "復活節"; | |||||
break; | |||||
case "劳动节": | |||||
tempName = "勞動節"; | |||||
break; | |||||
case "端午节": | |||||
tempName = "端午節"; | |||||
break; | |||||
case "重阳节": | |||||
tempName = "重陽節"; | |||||
break; | |||||
case "圣诞节后的第一个工作日": | |||||
tempName = "聖誕節後的第一个工作日"; | |||||
break; | |||||
default: | |||||
tempName = ele.name; | |||||
break; | |||||
} | |||||
return { | |||||
date: `${tempYear}-${tempMonth}-${tempDate}`, | |||||
title: tempName, | |||||
extendedProps: { calendar: "holiday" }, | |||||
}; | |||||
}); | |||||
}); | |||||
}; |
@@ -1,20 +1,28 @@ | |||||
"use client"; | "use client"; | ||||
import { HolidaysList, HolidaysResult } from "@/app/api/holidays"; | |||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, Stack } from '@mui/material/'; | |||||
import { HolidaysList } from "@/app/api/holidays"; | |||||
import React, { useCallback, useMemo, useState } from "react"; | |||||
import { Button, Stack } from "@mui/material/"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import FullCalendar from '@fullcalendar/react' | |||||
import dayGridPlugin from '@fullcalendar/daygrid' // a plugin! | |||||
import interactionPlugin from "@fullcalendar/interaction" // needed for dayClick | |||||
import listPlugin from '@fullcalendar/list'; | |||||
import Holidays from "date-holidays"; | |||||
import FullCalendar from "@fullcalendar/react"; | |||||
import dayGridPlugin from "@fullcalendar/daygrid"; // a plugin! | |||||
import interactionPlugin from "@fullcalendar/interaction"; // needed for dayClick | |||||
import listPlugin from "@fullcalendar/list"; | |||||
import CompanyHolidayDialog from "./CompanyHolidayDialog"; | import CompanyHolidayDialog from "./CompanyHolidayDialog"; | ||||
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form"; | |||||
import { EventBusy } from "@mui/icons-material"; | |||||
import { deleteCompanyHoliday, saveCompanyHoliday } from "@/app/api/holidays/actions"; | |||||
import { | |||||
FormProvider, | |||||
SubmitErrorHandler, | |||||
SubmitHandler, | |||||
useForm, | |||||
useFormContext, | |||||
} from "react-hook-form"; | |||||
import { | |||||
deleteCompanyHoliday, | |||||
saveCompanyHoliday, | |||||
} from "@/app/api/holidays/actions"; | |||||
import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
import { deleteDialog, submitDialog } from "../Swal/CustomAlerts"; | import { deleteDialog, submitDialog } from "../Swal/CustomAlerts"; | ||||
import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||||
interface Props { | interface Props { | ||||
holidays: HolidaysList[]; | holidays: HolidaysList[]; | ||||
@@ -23,118 +31,47 @@ interface Props { | |||||
const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | ||||
const { t } = useTranslation("holidays"); | const { t } = useTranslation("holidays"); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
const formValues = useFormContext(); | |||||
const formValues = useFormContext(); | |||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const hd = new Holidays('HK') | |||||
console.log(holidays) | |||||
const companyHolidays = useMemo( | |||||
() => [...getPublicHolidaysForNYears(2), ...holidays], | |||||
[holidays], | |||||
); | |||||
const [companyHolidays, setCompanyHolidays] = useState<HolidaysList[]>([]) | |||||
const [dateContent, setDateContent] = useState<{ date: string }>({date: ''}) | |||||
const [dateContent, setDateContent] = useState<{ date: string }>({ | |||||
date: "", | |||||
}); | |||||
const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
const [isEdit, setIsEdit] = useState(false); | const [isEdit, setIsEdit] = useState(false); | ||||
const [editable, setEditable] = useState(true); | const [editable, setEditable] = useState(true); | ||||
const handleClose = () => { | const handleClose = () => { | ||||
setOpen(false); | setOpen(false); | ||||
setEditable(true) | |||||
setIsEdit(false) | |||||
formProps.setValue("name", "") | |||||
formProps.setValue("id", null) | |||||
setEditable(true); | |||||
setIsEdit(false); | |||||
formProps.setValue("name", ""); | |||||
formProps.setValue("id", null); | |||||
}; | }; | ||||
const getPublicHolidaysList = () => { | |||||
const currentYear = new Date().getFullYear() | |||||
const currentYearHolidays = hd.getHolidays(currentYear) | |||||
const nextYearHolidays = hd.getHolidays(currentYear + 1) | |||||
const events_cyhd = currentYearHolidays.map(ele => { | |||||
const tempDay = new Date(ele.date) | |||||
const tempYear = tempDay.getFullYear() | |||||
const tempMonth = tempDay.getMonth() + 1 < 10 ? `0${ tempDay.getMonth() + 1}` : tempDay.getMonth() + 1 | |||||
const tempDate = tempDay.getDate() < 10 ? `0${tempDay.getDate()}` : tempDay.getDate() | |||||
let tempName = "" | |||||
switch (ele.name) { | |||||
case "复活节": | |||||
tempName = "復活節" | |||||
break | |||||
case "劳动节": | |||||
tempName = "勞動節" | |||||
break | |||||
case "端午节": | |||||
tempName = "端午節" | |||||
break | |||||
case "重阳节": | |||||
tempName = "重陽節" | |||||
break | |||||
case "圣诞节后的第一个工作日": | |||||
tempName = "聖誕節後的第一个工作日" | |||||
break | |||||
default: | |||||
tempName = ele.name | |||||
break | |||||
} | |||||
return {date: `${tempYear}-${tempMonth}-${tempDate}`, title: tempName, extendedProps: {calendar: 'holiday'}} | |||||
}) | |||||
const events_nyhd = nextYearHolidays.map(ele => { | |||||
const tempDay = new Date(ele.date) | |||||
const tempYear = tempDay.getFullYear() | |||||
const tempMonth = tempDay.getMonth() + 1 < 10 ? `0${ tempDay.getMonth() + 1}` : tempDay.getMonth() + 1 | |||||
const tempDate = tempDay.getDate() < 10 ? `0${tempDay.getDate()}` : tempDay.getDate() | |||||
let tempName = "" | |||||
switch (ele.name) { | |||||
case "复活节": | |||||
tempName = "復活節" | |||||
break | |||||
case "劳动节": | |||||
tempName = "勞動節" | |||||
break | |||||
case "端午节": | |||||
tempName = "端午節" | |||||
break | |||||
case "重阳节": | |||||
tempName = "重陽節" | |||||
break | |||||
case "圣诞节后的第一个工作日": | |||||
tempName = "聖誕節後的第一个工作日" | |||||
break | |||||
default: | |||||
tempName = ele.name | |||||
break | |||||
} | |||||
return {date: `${tempYear}-${tempMonth}-${tempDate}`, title: tempName, extendedProps: {calendar: 'holiday'}} | |||||
}) | |||||
setCompanyHolidays([...events_cyhd, ...events_nyhd, ...holidays] as HolidaysList[]) | |||||
} | |||||
useEffect(()=>{ | |||||
getPublicHolidaysList() | |||||
},[]) | |||||
useEffect(()=>{ | |||||
},[holidays]) | |||||
const handleDateClick = (event:any) => { | |||||
const handleDateClick = (event: any) => { | |||||
// console.log(event.dateStr) | // console.log(event.dateStr) | ||||
setDateContent({date: event.dateStr}) | |||||
setDateContent({ date: event.dateStr }); | |||||
setOpen(true); | setOpen(true); | ||||
} | |||||
const handleEventClick = (event:any) => { | |||||
}; | |||||
const handleEventClick = (event: any) => { | |||||
// event.event.id: if id !== "", holiday is created by company | // event.event.id: if id !== "", holiday is created by company | ||||
console.log(event.event.id) | |||||
if (event.event.id === null || event.event.id === ""){ | |||||
setEditable(false) | |||||
console.log(event.event.id); | |||||
if (event.event.id === null || event.event.id === "") { | |||||
setEditable(false); | |||||
} | } | ||||
formProps.setValue("name", event.event.title) | |||||
formProps.setValue("id", event.event.id) | |||||
setDateContent({date: event.event.startStr}) | |||||
formProps.setValue("name", event.event.title); | |||||
formProps.setValue("id", event.event.id); | |||||
setDateContent({ date: event.event.startStr }); | |||||
setOpen(true); | setOpen(true); | ||||
setIsEdit(true); | setIsEdit(true); | ||||
} | |||||
}; | |||||
const onSubmit = useCallback<SubmitHandler<any>>( | const onSubmit = useCallback<SubmitHandler<any>>( | ||||
async (data) => { | async (data) => { | ||||
@@ -142,11 +79,11 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||||
// console.log(data); | // console.log(data); | ||||
setServerError(""); | setServerError(""); | ||||
submitDialog(async () => { | submitDialog(async () => { | ||||
await saveCompanyHoliday(data) | |||||
window.location.reload() | |||||
await saveCompanyHoliday(data); | |||||
window.location.reload(); | |||||
setOpen(false); | setOpen(false); | ||||
setIsEdit(false); | setIsEdit(false); | ||||
}, t) | |||||
}, t); | |||||
} catch (e) { | } catch (e) { | ||||
console.log(e); | console.log(e); | ||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
@@ -155,12 +92,12 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||||
[t, router], | [t, router], | ||||
); | ); | ||||
const handleDelete = async (event:any) => { | |||||
const handleDelete = async (event: any) => { | |||||
try { | try { | ||||
setServerError(""); | setServerError(""); | ||||
deleteDialog(async () => { | deleteDialog(async () => { | ||||
await deleteCompanyHoliday(parseInt(formProps.getValues("id"))) | |||||
window.location.reload() | |||||
await deleteCompanyHoliday(parseInt(formProps.getValues("id"))); | |||||
window.location.reload(); | |||||
setOpen(false); | setOpen(false); | ||||
setIsEdit(false); | setIsEdit(false); | ||||
}, t); | }, t); | ||||
@@ -168,58 +105,72 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||||
console.log(e); | console.log(e); | ||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
} | } | ||||
} | |||||
const onSubmitError = useCallback<SubmitErrorHandler<any>>( | |||||
(errors) => { | |||||
console.log(errors) | |||||
}, | |||||
[], | |||||
); | |||||
}; | |||||
const onSubmitError = useCallback<SubmitErrorHandler<any>>((errors) => { | |||||
console.log(errors); | |||||
}, []); | |||||
const formProps = useForm<any>({ | const formProps = useForm<any>({ | ||||
defaultValues: { | defaultValues: { | ||||
id: null, | id: null, | ||||
name: "" | |||||
name: "", | |||||
}, | }, | ||||
}); | }); | ||||
return ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | |||||
<FullCalendar | |||||
plugins={[ dayGridPlugin, interactionPlugin, listPlugin ]} | |||||
initialView="dayGridMonth" | |||||
events={companyHolidays} | |||||
eventColor='#ff0000' | |||||
dateClick={handleDateClick} | |||||
eventClick={handleEventClick} | |||||
headerToolbar={{ | |||||
start: "today prev next", | |||||
end: "dayGridMonth listMonth" | |||||
}} | |||||
buttonText={{ | |||||
month: t("Calender View"), | |||||
list: t("List View"), | |||||
today: t("Today") | |||||
}} | |||||
/> | |||||
<CompanyHolidayDialog | |||||
open={open} | |||||
onClose={handleClose} | |||||
title={!editable ? "Bank Holiday" : isEdit ? "Edit Holiday" : "Create Holiday"} | |||||
content={dateContent} | |||||
actions={ | |||||
<Stack direction="row" justifyContent="flex-end" gap={1} component="form" onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}> | |||||
<Button onClick={handleClose}>Close</Button> | |||||
{isEdit && <Button disabled={!editable} onClick={handleDelete}>Delete</Button>} | |||||
<Button disabled={!editable} type="submit">Submit</Button> | |||||
</Stack> | |||||
} | |||||
editable={editable} | |||||
/> | |||||
</FormProvider> | |||||
<FormProvider {...formProps}> | |||||
<FullCalendar | |||||
plugins={[dayGridPlugin, interactionPlugin, listPlugin]} | |||||
initialView="dayGridMonth" | |||||
events={companyHolidays} | |||||
eventColor="#ff0000" | |||||
dateClick={handleDateClick} | |||||
eventClick={handleEventClick} | |||||
headerToolbar={{ | |||||
start: "today prev next", | |||||
end: "dayGridMonth listMonth", | |||||
}} | |||||
buttonText={{ | |||||
month: t("Calender View"), | |||||
list: t("List View"), | |||||
today: t("Today"), | |||||
}} | |||||
/> | |||||
<CompanyHolidayDialog | |||||
open={open} | |||||
onClose={handleClose} | |||||
title={ | |||||
!editable | |||||
? "Bank Holiday" | |||||
: isEdit | |||||
? "Edit Holiday" | |||||
: "Create Holiday" | |||||
} | |||||
content={dateContent} | |||||
actions={ | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="flex-end" | |||||
gap={1} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
> | |||||
<Button onClick={handleClose}>Close</Button> | |||||
{isEdit && ( | |||||
<Button disabled={!editable} onClick={handleDelete}> | |||||
Delete | |||||
</Button> | |||||
)} | |||||
<Button disabled={!editable} type="submit"> | |||||
Submit | |||||
</Button> | |||||
</Stack> | |||||
} | |||||
editable={editable} | |||||
/> | |||||
</FormProvider> | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
@@ -24,7 +24,7 @@ import LeaveTable from "../LeaveTable"; | |||||
import { LeaveType } from "@/app/api/timesheets"; | import { LeaveType } from "@/app/api/timesheets"; | ||||
import FullscreenModal from "../FullscreenModal"; | import FullscreenModal from "../FullscreenModal"; | ||||
import MobileLeaveTable from "../LeaveTable/MobileLeaveTable"; | import MobileLeaveTable from "../LeaveTable/MobileLeaveTable"; | ||||
import useIsMobile from "../utils/useIsMobile"; | |||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
interface Props { | interface Props { | ||||
isOpen: boolean; | isOpen: boolean; | ||||
@@ -18,42 +18,54 @@ import Settings from "@mui/icons-material/Settings"; | |||||
import Analytics from "@mui/icons-material/Analytics"; | import Analytics from "@mui/icons-material/Analytics"; | ||||
import Payments from "@mui/icons-material/Payments"; | import Payments from "@mui/icons-material/Payments"; | ||||
import Staff from "@mui/icons-material/PeopleAlt"; | import Staff from "@mui/icons-material/PeopleAlt"; | ||||
import Company from '@mui/icons-material/Store'; | |||||
import Department from '@mui/icons-material/Diversity3'; | |||||
import Position from '@mui/icons-material/Paragliding'; | |||||
import Salary from '@mui/icons-material/AttachMoney'; | |||||
import Team from '@mui/icons-material/Paragliding'; | |||||
import Holiday from '@mui/icons-material/CalendarMonth'; | |||||
import Company from "@mui/icons-material/Store"; | |||||
import Department from "@mui/icons-material/Diversity3"; | |||||
import Position from "@mui/icons-material/Paragliding"; | |||||
import Salary from "@mui/icons-material/AttachMoney"; | |||||
import Team from "@mui/icons-material/Paragliding"; | |||||
import Holiday from "@mui/icons-material/CalendarMonth"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import Typography from "@mui/material/Typography"; | |||||
import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||
import Link from "next/link"; | import Link from "next/link"; | ||||
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | ||||
import Logo from "../Logo"; | import Logo from "../Logo"; | ||||
import GroupIcon from '@mui/icons-material/Group'; | |||||
import BusinessIcon from '@mui/icons-material/Business'; | |||||
import ViewWeekIcon from '@mui/icons-material/ViewWeek'; | |||||
import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; | |||||
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; | |||||
import { GENERATE_REPORTS, MAINTAIN_MASTERDATA, MAINTAIN_USER, VIEW_MASTERDATA, VIEW_USER } from "@/middleware"; | |||||
import GroupIcon from "@mui/icons-material/Group"; | |||||
import BusinessIcon from "@mui/icons-material/Business"; | |||||
import ViewWeekIcon from "@mui/icons-material/ViewWeek"; | |||||
import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; | |||||
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; | |||||
import { | |||||
GENERATE_REPORTS, | |||||
MAINTAIN_MASTERDATA, | |||||
MAINTAIN_USER, | |||||
VIEW_MASTERDATA, | |||||
VIEW_USER, | |||||
} from "@/middleware"; | |||||
import { SessionWithAbilities } from "../AppBar/NavigationToggle"; | import { SessionWithAbilities } from "../AppBar/NavigationToggle"; | ||||
import { authOptions } from "@/config/authConfig"; | import { authOptions } from "@/config/authConfig"; | ||||
import { getServerSession } from "next-auth"; | import { getServerSession } from "next-auth"; | ||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
interface NavigationItem { | interface NavigationItem { | ||||
icon: React.ReactNode; | icon: React.ReactNode; | ||||
label: string; | label: string; | ||||
path: string; | path: string; | ||||
isHidden?: boolean; | isHidden?: boolean; | ||||
showOnMobile?: boolean; | |||||
children?: NavigationItem[]; | children?: NavigationItem[]; | ||||
} | } | ||||
interface Props { | interface Props { | ||||
abilities?: string[] | |||||
abilities?: string[]; | |||||
} | } | ||||
const NavigationContent: React.FC<Props> = ({ abilities }) => { | const NavigationContent: React.FC<Props> = ({ abilities }) => { | ||||
const navigationItems: NavigationItem[] = [ | const navigationItems: NavigationItem[] = [ | ||||
{ icon: <WorkHistory />, label: "User Workspace", path: "/home" }, | |||||
{ | |||||
icon: <WorkHistory />, | |||||
label: "User Workspace", | |||||
path: "/home", | |||||
showOnMobile: true, | |||||
}, | |||||
{ | { | ||||
icon: <Dashboard />, | icon: <Dashboard />, | ||||
label: "Dashboard", | label: "Dashboard", | ||||
@@ -93,7 +105,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
icon: <ViewWeekIcon />, | icon: <ViewWeekIcon />, | ||||
label: "Project Resource Summary", | label: "Project Resource Summary", | ||||
path: "/dashboard/ProjectResourceSummary", | path: "/dashboard/ProjectResourceSummary", | ||||
} | |||||
}, | |||||
], | ], | ||||
}, | }, | ||||
{ | { | ||||
@@ -116,40 +128,109 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
{ icon: <Assignment />, label: "Project Management", path: "/projects" }, | { icon: <Assignment />, label: "Project Management", path: "/projects" }, | ||||
{ icon: <Task />, label: "Task Template", path: "/tasks" }, | { icon: <Task />, label: "Task Template", path: "/tasks" }, | ||||
{ icon: <Payments />, label: "Invoice", path: "/invoice" }, | { icon: <Payments />, label: "Invoice", path: "/invoice" }, | ||||
{ icon: <Analytics />, label: "Analysis Report", path: "", isHidden: ![GENERATE_REPORTS].some((ability) => abilities!!.includes(ability)), | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Analysis Report", | |||||
path: "", | |||||
isHidden: ![GENERATE_REPORTS].some((ability) => | |||||
abilities!.includes(ability), | |||||
), | |||||
children: [ | children: [ | ||||
{icon: <Analytics />, label:"Late Start Report", path: "/analytics/LateStartReport"}, | |||||
{icon: <Analytics />, label:"Delay Report", path: "/analytics/DelayReport"}, | |||||
{icon: <Analytics />, label:"Resource Overconsumption Report", path: "/analytics/ResourceOverconsumptionReport"}, | |||||
{icon: <Analytics />, label:"Cost and Expense Report", path: "/analytics/CostandExpenseReport"}, | |||||
{icon: <Analytics />, label:"Completion Report", path: "/analytics/ProjectCompletionReport"}, | |||||
{icon: <Analytics />, label:"Completion Report with Outstanding Un-billed Hours Report", path: "/analytics/ProjectCompletionReportWO"}, | |||||
{icon: <Analytics />, label:"Project Claims Report", path: "/analytics/ProjectClaimsReport"}, | |||||
{icon: <Analytics />, label:"Project P&L Report", path: "/analytics/ProjectPLReport"}, | |||||
{icon: <Analytics />, label:"Financial Status Report", path: "/analytics/FinancialStatusReport"}, | |||||
{icon: <Analytics />, label:"Project Cash Flow Report", path: "/analytics/ProjectCashFlowReport"}, | |||||
{icon: <Analytics />, label:"Staff Monthly Work Hours Analysis Report", path: "/analytics/StaffMonthlyWorkHoursAnalysisReport"}, | |||||
], | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Late Start Report", | |||||
path: "/analytics/LateStartReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Delay Report", | |||||
path: "/analytics/DelayReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Resource Overconsumption Report", | |||||
path: "/analytics/ResourceOverconsumptionReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Cost and Expense Report", | |||||
path: "/analytics/CostandExpenseReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Completion Report", | |||||
path: "/analytics/ProjectCompletionReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Completion Report with Outstanding Un-billed Hours Report", | |||||
path: "/analytics/ProjectCompletionReportWO", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Project Claims Report", | |||||
path: "/analytics/ProjectClaimsReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Project P&L Report", | |||||
path: "/analytics/ProjectPLReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Financial Status Report", | |||||
path: "/analytics/FinancialStatusReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Project Cash Flow Report", | |||||
path: "/analytics/ProjectCashFlowReport", | |||||
}, | |||||
{ | |||||
icon: <Analytics />, | |||||
label: "Staff Monthly Work Hours Analysis Report", | |||||
path: "/analytics/StaffMonthlyWorkHoursAnalysisReport", | |||||
}, | |||||
], | |||||
}, | |||||
{ | { | ||||
icon: <Settings />, label: "Setting", path: "", isHidden: ![VIEW_MASTERDATA, MAINTAIN_MASTERDATA].some((ability) => abilities!!.includes(ability)), | |||||
icon: <Settings />, | |||||
label: "Setting", | |||||
path: "", | |||||
isHidden: ![VIEW_MASTERDATA, MAINTAIN_MASTERDATA].some((ability) => | |||||
abilities!.includes(ability), | |||||
), | |||||
children: [ | children: [ | ||||
{ icon: <GroupIcon />, label: "Client", path: "/settings/customer" }, | { icon: <GroupIcon />, label: "Client", path: "/settings/customer" }, | ||||
{ icon: <BusinessIcon />, label: "Subsidiary", path: "/settings/subsidiary" }, | |||||
{ | |||||
icon: <BusinessIcon />, | |||||
label: "Subsidiary", | |||||
path: "/settings/subsidiary", | |||||
}, | |||||
{ icon: <Staff />, label: "Staff", path: "/settings/staff" }, | { icon: <Staff />, label: "Staff", path: "/settings/staff" }, | ||||
{ icon: <Company />, label: "Company", path: "/settings/company" }, | { icon: <Company />, label: "Company", path: "/settings/company" }, | ||||
{ icon: <EmojiEventsIcon />, label: "Skill", path: "/settings/skill" }, | { icon: <EmojiEventsIcon />, label: "Skill", path: "/settings/skill" }, | ||||
{ icon: <Department />, label: "Department", path: "/settings/department" }, | |||||
{ | |||||
icon: <Department />, | |||||
label: "Department", | |||||
path: "/settings/department", | |||||
}, | |||||
{ icon: <Position />, label: "Position", path: "/settings/position" }, | { icon: <Position />, label: "Position", path: "/settings/position" }, | ||||
{ icon: <Salary />, label: "Salary", path: "/settings/salary" }, | { icon: <Salary />, label: "Salary", path: "/settings/salary" }, | ||||
{ icon: <Team />, label: "Team", path: "/settings/team" }, | { icon: <Team />, label: "Team", path: "/settings/team" }, | ||||
// { icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" }, | // { icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" }, | ||||
{ icon: <ManageAccountsIcon />, label: "User Group", path: "/settings/group" }, | |||||
{ | |||||
icon: <ManageAccountsIcon />, | |||||
label: "User Group", | |||||
path: "/settings/group", | |||||
}, | |||||
{ icon: <Holiday />, label: "Holiday", path: "/settings/holiday" }, | { icon: <Holiday />, label: "Holiday", path: "/settings/holiday" }, | ||||
], | ], | ||||
}, | }, | ||||
]; | ]; | ||||
const isMobile = useIsMobile(); | |||||
const { t } = useTranslation("common"); | const { t } = useTranslation("common"); | ||||
const pathname = usePathname(); | const pathname = usePathname(); | ||||
const [openItems, setOpenItems] = React.useState<string[]>([]); | const [openItems, setOpenItems] = React.useState<string[]>([]); | ||||
@@ -197,7 +278,11 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
</Box> | </Box> | ||||
<Divider /> | <Divider /> | ||||
<List component="nav"> | <List component="nav"> | ||||
{navigationItems.filter(item => item.isHidden !== true).map((item) => renderNavigationItem(item))} | |||||
{navigationItems | |||||
.filter( | |||||
(item) => !item.isHidden && (isMobile ? item.showOnMobile : true), | |||||
) | |||||
.map((item) => renderNavigationItem(item))} | |||||
{/* {navigationItems.map(({ icon, label, path }, index) => { | {/* {navigationItems.map(({ icon, label, path }, index) => { | ||||
return ( | return ( | ||||
<Box | <Box | ||||
@@ -18,7 +18,7 @@ import { ArrowBack } from "@mui/icons-material"; | |||||
import PastEntryList from "./PastEntryList"; | import PastEntryList from "./PastEntryList"; | ||||
import { ProjectWithTasks } from "@/app/api/projects"; | import { ProjectWithTasks } from "@/app/api/projects"; | ||||
import { LeaveType } from "@/app/api/timesheets"; | import { LeaveType } from "@/app/api/timesheets"; | ||||
import useIsMobile from "../utils/useIsMobile"; | |||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
import FullscreenModal from "../FullscreenModal"; | import FullscreenModal from "../FullscreenModal"; | ||||
interface Props extends Omit<PastEntryCalendarProps, "onDateSelect"> { | interface Props extends Omit<PastEntryCalendarProps, "onDateSelect"> { | ||||
@@ -95,7 +95,7 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||||
return isMobile ? ( | return isMobile ? ( | ||||
<FullscreenModal open={open} onClose={onClose} closeModal={onClose}> | <FullscreenModal open={open} onClose={onClose} closeModal={onClose}> | ||||
<Box display="flex" flexDirection="column" gap={2} height="100%"> | <Box display="flex" flexDirection="column" gap={2} height="100%"> | ||||
<Typography variant="h6" flex="none" paddingInline={2}> | |||||
<Typography variant="h6" flex="none" padding={2}> | |||||
{t("Past Entries")} | {t("Past Entries")} | ||||
</Typography> | </Typography> | ||||
<Box | <Box | ||||
@@ -109,13 +109,15 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||||
{content} | {content} | ||||
</Box> | </Box> | ||||
<Box padding={2} display="flex" justifyContent="flex-end"> | <Box padding={2} display="flex" justifyContent="flex-end"> | ||||
<Button | |||||
variant="outlined" | |||||
startIcon={<ArrowBack />} | |||||
onClick={clearDate} | |||||
> | |||||
{t("Back")} | |||||
</Button> | |||||
{selectedDate && ( | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<ArrowBack />} | |||||
onClick={clearDate} | |||||
> | |||||
{t("Back")} | |||||
</Button> | |||||
)} | |||||
</Box> | </Box> | ||||
</Box> | </Box> | ||||
</FullscreenModal> | </FullscreenModal> | ||||
@@ -24,7 +24,7 @@ import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; | import { AssignedProject, ProjectWithTasks } from "@/app/api/projects"; | ||||
import FullscreenModal from "../FullscreenModal"; | import FullscreenModal from "../FullscreenModal"; | ||||
import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable"; | import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable"; | ||||
import useIsMobile from "../utils/useIsMobile"; | |||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
interface Props { | interface Props { | ||||
isOpen: boolean; | isOpen: boolean; | ||||
@@ -3,7 +3,7 @@ | |||||
import React from "react"; | import React from "react"; | ||||
import TransferList, { TransferListProps } from "./TransferList"; | import TransferList, { TransferListProps } from "./TransferList"; | ||||
import MultiSelectList from "./MultiSelectList"; | import MultiSelectList from "./MultiSelectList"; | ||||
import useIsMobile from "../utils/useIsMobile"; | |||||
import useIsMobile from "@/app/utils/useIsMobile"; | |||||
const TransferListWrapper: React.FC<TransferListProps> = (props) => { | const TransferListWrapper: React.FC<TransferListProps> = (props) => { | ||||
const matches = useIsMobile(); | const matches = useIsMobile(); | ||||
@@ -18,6 +18,7 @@ import LeaveModal from "../LeaveModal"; | |||||
import { LeaveType } from "@/app/api/timesheets"; | import { LeaveType } 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"; | |||||
export interface Props { | export interface Props { | ||||
leaveTypes: LeaveType[]; | leaveTypes: LeaveType[]; | ||||
@@ -26,6 +27,7 @@ export interface Props { | |||||
username: string; | username: string; | ||||
defaultLeaveRecords: RecordLeaveInput; | defaultLeaveRecords: RecordLeaveInput; | ||||
defaultTimesheets: RecordTimesheetInput; | defaultTimesheets: RecordTimesheetInput; | ||||
holidays: HolidaysResult[]; | |||||
} | } | ||||
const UserWorkspacePage: React.FC<Props> = ({ | const UserWorkspacePage: React.FC<Props> = ({ | ||||
@@ -35,6 +37,7 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
username, | username, | ||||
defaultLeaveRecords, | defaultLeaveRecords, | ||||
defaultTimesheets, | defaultTimesheets, | ||||
holidays | |||||
}) => { | }) => { | ||||
const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | ||||
const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | ||||
@@ -8,20 +8,28 @@ import { | |||||
fetchLeaves, | fetchLeaves, | ||||
fetchTimesheets, | fetchTimesheets, | ||||
} from "@/app/api/timesheets"; | } from "@/app/api/timesheets"; | ||||
import { fetchHolidays } from "@/app/api/holidays"; | |||||
interface Props { | interface Props { | ||||
username: string; | username: string; | ||||
} | } | ||||
const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | ||||
const [assignedProjects, allProjects, timesheets, leaves, leaveTypes] = | |||||
await Promise.all([ | |||||
fetchAssignedProjects(username), | |||||
fetchProjectWithTasks(), | |||||
fetchTimesheets(username), | |||||
fetchLeaves(username), | |||||
fetchLeaveTypes(), | |||||
]); | |||||
const [ | |||||
assignedProjects, | |||||
allProjects, | |||||
timesheets, | |||||
leaves, | |||||
leaveTypes, | |||||
holidays, | |||||
] = await Promise.all([ | |||||
fetchAssignedProjects(username), | |||||
fetchProjectWithTasks(), | |||||
fetchTimesheets(username), | |||||
fetchLeaves(username), | |||||
fetchLeaveTypes(), | |||||
fetchHolidays(), | |||||
]); | |||||
return ( | return ( | ||||
<UserWorkspacePage | <UserWorkspacePage | ||||
@@ -31,6 +39,7 @@ const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | |||||
defaultTimesheets={timesheets} | defaultTimesheets={timesheets} | ||||
defaultLeaveRecords={leaves} | defaultLeaveRecords={leaves} | ||||
leaveTypes={leaveTypes} | leaveTypes={leaveTypes} | ||||
holidays={holidays} | |||||
/> | /> | ||||
); | ); | ||||
}; | }; | ||||