| @@ -0,0 +1,21 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { I18nProvider } from "@/i18n"; | |||||
| import { Suspense } from "react"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import Bspp from "@/components/Bspp"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const CompanyTeamCashFlow: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| BSPP | |||||
| </Typography> | |||||
| <Bspp /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default CompanyTeamCashFlow; | |||||
| @@ -1,3 +1,4 @@ | |||||
| import { searchParamsProps } from "@/app/utils/fetchUtil"; | |||||
| import EditPosition from "@/components/EditPosition"; | import EditPosition from "@/components/EditPosition"; | ||||
| import EditUserGroup from "@/components/EditUserGroup"; | import EditUserGroup from "@/components/EditUserGroup"; | ||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| @@ -8,7 +9,9 @@ export const metadata: Metadata = { | |||||
| title: "Edit User Group", | title: "Edit User Group", | ||||
| }; | }; | ||||
| const Group: React.FC = async () => { | |||||
| const Group: React.FC<searchParamsProps> = async ({ | |||||
| searchParams, | |||||
| }) => { | |||||
| const { t } = await getServerI18n("group"); | const { t } = await getServerI18n("group"); | ||||
| // Preload necessary dependencies | // Preload necessary dependencies | ||||
| @@ -17,7 +20,7 @@ const Group: React.FC = async () => { | |||||
| <> | <> | ||||
| {/* <Typography variant="h4">{t("Edit User Group")}</Typography> */} | {/* <Typography variant="h4">{t("Edit User Group")}</Typography> */} | ||||
| <I18nProvider namespaces={["group"]}> | <I18nProvider namespaces={["group"]}> | ||||
| <EditUserGroup /> | |||||
| <EditUserGroup id={parseInt(searchParams.id as string)}/> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -14,8 +14,21 @@ export interface UserGroupResult { | |||||
| description: string; | description: string; | ||||
| } | } | ||||
| export type IndivUserGroup = { | |||||
| authIds: number[]; | |||||
| data: any; | |||||
| userIds: number[]; | |||||
| } | |||||
| export const fetchGroup = cache(async () => { | export const fetchGroup = cache(async () => { | ||||
| return serverFetchJson<Records>(`${BASE_API_URL}/group`, { | return serverFetchJson<Records>(`${BASE_API_URL}/group`, { | ||||
| next: { tags: ["group"] }, | next: { tags: ["group"] }, | ||||
| }); | }); | ||||
| }); | }); | ||||
| export const fetchIndivGroup = cache(async (id: number) => { | |||||
| return serverFetchJson<IndivUserGroup>(`${BASE_API_URL}/group/${id}`, { | |||||
| next: { tags: ["group"] }, | |||||
| }); | |||||
| }); | |||||
| @@ -46,7 +46,7 @@ export interface ProjectPotentialDelayReportRequest { | |||||
| // - Monthly Work Hours Report | // - Monthly Work Hours Report | ||||
| export interface MonthlyWorkHoursReportFilter { | export interface MonthlyWorkHoursReportFilter { | ||||
| staff: string[]; | staff: string[]; | ||||
| date: any; | |||||
| date: string; | |||||
| } | } | ||||
| export interface MonthlyWorkHoursReportRequest { | export interface MonthlyWorkHoursReportRequest { | ||||
| @@ -20,17 +20,17 @@ export interface CreateStaffInputs { | |||||
| companyId: number; | companyId: number; | ||||
| salaryId: number; | salaryId: number; | ||||
| skillSetId?: number[]; | skillSetId?: number[]; | ||||
| joinDate: string; | |||||
| joinDate?: string; | |||||
| currentPositionId: number; | currentPositionId: number; | ||||
| joinPositionId: number; | |||||
| joinPositionId?: number; | |||||
| gradeId?: number; | gradeId?: number; | ||||
| teamId?: number | teamId?: number | ||||
| departmentId: number; | |||||
| departmentId?: number; | |||||
| phone1: string; | phone1: string; | ||||
| phone2?: string; | phone2?: string; | ||||
| email: string; | email: string; | ||||
| emergContactName: string; | |||||
| emergContactPhone: string; | |||||
| emergContactName?: string; | |||||
| emergContactPhone?: string; | |||||
| employType: string; | employType: string; | ||||
| departDate?: string; | departDate?: string; | ||||
| departReason?: string; | departReason?: string; | ||||
| @@ -7,6 +7,7 @@ import { UserDetail, UserResult } from "."; | |||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| export interface UserInputs { | export interface UserInputs { | ||||
| username: string; | |||||
| name: string; | name: string; | ||||
| email?: string; | email?: string; | ||||
| addAuthIds?: number[]; | addAuthIds?: number[]; | ||||
| @@ -63,7 +63,7 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||||
| {profileName} | {profileName} | ||||
| </Typography> | </Typography> | ||||
| <Divider /> | <Divider /> | ||||
| <MenuItem onClick={() => { router.replace("/settings/changepassword") }}>{t("Change Password")}</MenuItem> | |||||
| <MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem> | |||||
| {language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>} | {language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>} | ||||
| {language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>} | {language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>} | ||||
| <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | ||||
| @@ -0,0 +1,404 @@ | |||||
| "use client"; | |||||
| import * as React from "react"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import { Card, CardHeader } from "@mui/material"; | |||||
| import ReactApexChart from "react-apexcharts"; | |||||
| import { ApexOptions } from "apexcharts"; | |||||
| import "../../app/global.css"; | |||||
| import { Input, Label } from "reactstrap"; | |||||
| import Select, { components } from "react-select"; | |||||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||||
| import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | |||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import dayjs, { Dayjs } from "dayjs"; | |||||
| import isBetweenPlugin from "dayjs/plugin/isBetween"; | |||||
| import { PickersDay, PickersDayProps } from "@mui/x-date-pickers/PickersDay"; | |||||
| import { styled } from "@mui/material/styles"; | |||||
| dayjs.extend(isBetweenPlugin); | |||||
| interface CustomPickerDayProps extends PickersDayProps<Dayjs> { | |||||
| isSelected: boolean; | |||||
| isHovered: boolean; | |||||
| } | |||||
| // Style Day | |||||
| const CustomPickersDay = styled(PickersDay, { | |||||
| shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isHovered", | |||||
| })<CustomPickerDayProps>(({ theme, isSelected, isHovered, day }) => ({ | |||||
| borderRadius: 0, | |||||
| ...(isSelected && { | |||||
| backgroundColor: theme.palette.primary.main, | |||||
| color: theme.palette.primary.contrastText, | |||||
| "&:hover, &:focus": { | |||||
| backgroundColor: theme.palette.primary.main, | |||||
| }, | |||||
| }), | |||||
| ...(isHovered && { | |||||
| backgroundColor: theme.palette.primary[theme.palette.mode], | |||||
| "&:hover, &:focus": { | |||||
| backgroundColor: theme.palette.primary[theme.palette.mode], | |||||
| }, | |||||
| }), | |||||
| ...(day.day() === 0 && { | |||||
| borderTopLeftRadius: "50%", | |||||
| borderBottomLeftRadius: "50%", | |||||
| }), | |||||
| ...(day.day() === 6 && { | |||||
| borderTopRightRadius: "50%", | |||||
| borderBottomRightRadius: "50%", | |||||
| }), | |||||
| })) as React.ComponentType<CustomPickerDayProps>; | |||||
| const isInSameWeek = (dayA: Dayjs, dayB: Dayjs | null | undefined) => { | |||||
| if (dayB == null) { | |||||
| return false; | |||||
| } | |||||
| return dayA.isSame(dayB, "week"); | |||||
| }; | |||||
| function Day( | |||||
| props: PickersDayProps<Dayjs> & { | |||||
| selectedDay?: Dayjs | null; | |||||
| hoveredDay?: Dayjs | null; | |||||
| }, | |||||
| ) { | |||||
| const { day, selectedDay, hoveredDay, ...other } = props; | |||||
| return ( | |||||
| <CustomPickersDay | |||||
| {...other} | |||||
| day={day} | |||||
| sx={{ px: 2.5 }} | |||||
| disableMargin | |||||
| selected={false} | |||||
| isSelected={isInSameWeek(day, selectedDay)} | |||||
| isHovered={isInSameWeek(day, hoveredDay)} | |||||
| /> | |||||
| ); | |||||
| } | |||||
| // Main function | |||||
| const Bspp: React.FC = () => { | |||||
| // Set Date | |||||
| const todayDate = new Date(); | |||||
| const firstDayOfWeek = new Date(); | |||||
| const lastDayOfWeek = new Date(); | |||||
| firstDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 1); | |||||
| lastDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 7); | |||||
| const [teamTotalManhoursSpentSelect, setTeamTotalManhoursSpentSelect]: any = | |||||
| React.useState("Weekly"); | |||||
| const weekDates: any[] = []; | |||||
| const monthDates: any[] = []; | |||||
| const currentDate = dayjs(); | |||||
| const sixMonthsAgo = currentDate.subtract(6, "month"); | |||||
| for (let i = 0; i < 7; i++) { | |||||
| const currentDate = new Date(firstDayOfWeek); | |||||
| currentDate.setDate(firstDayOfWeek.getDate() + i); | |||||
| const formattedDate = dayjs(currentDate).format("DD MMM (ddd)"); | |||||
| weekDates.push(formattedDate); | |||||
| } | |||||
| for ( | |||||
| let date = sixMonthsAgo.clone(); | |||||
| date.isBefore(currentDate, "month"); | |||||
| date = date.add(1, "month") | |||||
| ) { | |||||
| monthDates.push(date.format("MM-YYYY")); | |||||
| } | |||||
| monthDates.push(currentDate.format("MM-YYYY")); | |||||
| const [teamTotalManhoursSpentPeriod, setTeamTotalManhoursSpentPeriod]: any[] = | |||||
| React.useState(weekDates); | |||||
| // Set fake Data | |||||
| const [ | |||||
| teamTotalManhoursSpentPlanData, | |||||
| setTeamTotalManhoursSpentPlanData, | |||||
| ]: any[] = React.useState([42, 42, 42, 42, 42, 0, 0]); | |||||
| const [ | |||||
| teamTotalManhoursSpentActualData, | |||||
| setTeamTotalManhoursSpentActualData, | |||||
| ]: any[] = React.useState([45, 42, 60, 42, 58, 0, 0]); | |||||
| const [hoveredDay, setHoveredDay] = React.useState<Dayjs | null>(null); | |||||
| const [value, setValue] = React.useState<Dayjs | null>(dayjs()); | |||||
| const [totalManHoursMonthlyFromValue, setTotalManHoursMonthlyFromValue] = | |||||
| React.useState<Dayjs>(dayjs(new Date()).subtract(6, "month")); | |||||
| const [totalManHoursMonthlyToValue, setTotalManHoursMonthlyToValue] = | |||||
| React.useState<Dayjs>(dayjs()); | |||||
| const [totalManHoursMaxValue, setTotalManHoursMaxValue] = React.useState(75); | |||||
| const teamOptions = [ | |||||
| { value: 1, label: "XXX Team" }, | |||||
| { value: 2, label: "YYY Team" }, | |||||
| { value: 3, label: "ZZZ Team" }, | |||||
| ]; | |||||
| // https://poe.com/ | |||||
| // https://apexcharts.com/docs/chart-types/line-chart/ | |||||
| // chart options | |||||
| const options: ApexOptions = { | |||||
| chart: { | |||||
| height: 350, | |||||
| type: "line", | |||||
| }, | |||||
| stroke: { | |||||
| width: [2, 2], | |||||
| }, | |||||
| plotOptions: { | |||||
| bar: { | |||||
| horizontal: false, | |||||
| distributed: false, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| enabled: true, | |||||
| }, | |||||
| xaxis: { | |||||
| categories: teamTotalManhoursSpentPeriod, | |||||
| }, | |||||
| yaxis: [ | |||||
| { | |||||
| title: { | |||||
| text: "Team Total Manhours Spent (Hour)", | |||||
| }, | |||||
| min: 0, | |||||
| max: totalManHoursMaxValue, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| ], | |||||
| grid: { | |||||
| borderColor: "#f1f1f1", | |||||
| }, | |||||
| annotations: {}, | |||||
| series: [ | |||||
| { | |||||
| name: "Planned", | |||||
| type: "line", | |||||
| color: "#efbe7d", | |||||
| data: teamTotalManhoursSpentPlanData, | |||||
| }, | |||||
| { | |||||
| name: "Actual", | |||||
| type: "line", | |||||
| color: "#7cd3f2", | |||||
| data: teamTotalManhoursSpentActualData, | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| // On click function | |||||
| const teamTotalManhoursSpentOnClick = (r: any) => { | |||||
| setTeamTotalManhoursSpentSelect(r); | |||||
| if (r === "Weekly") { | |||||
| setValue(dayjs(new Date())); | |||||
| setTeamTotalManhoursSpentPeriod(weekDates); | |||||
| setTeamTotalManhoursSpentPlanData([42, 42, 42, 42, 42, 0, 0]); | |||||
| setTeamTotalManhoursSpentActualData([45, 42, 60, 42, 58, 0, 0]); | |||||
| setTotalManHoursMaxValue(75); | |||||
| } else if (r === "Monthly") { | |||||
| setTeamTotalManhoursSpentPeriod(monthDates); | |||||
| setTeamTotalManhoursSpentPlanData([840, 840, 840, 840, 840, 840]); | |||||
| setTeamTotalManhoursSpentActualData([900, 840, 1200, 840, 1160, 840]); | |||||
| setTotalManHoursMaxValue(1250); | |||||
| } | |||||
| }; | |||||
| const selectWeeklyPeriod = (r: any) => { | |||||
| const selectDate = new Date(r); | |||||
| const firstDayOfWeek = new Date(); | |||||
| firstDayOfWeek.setDate(selectDate.getDate() - selectDate.getDay() + 0); | |||||
| const weekDates: any[] = []; | |||||
| for (let i = 0; i < 7; i++) { | |||||
| const currentDate = new Date(firstDayOfWeek); | |||||
| currentDate.setDate(firstDayOfWeek.getDate() + i); | |||||
| const formattedDate = dayjs(currentDate).format("DD MMM (ddd)"); | |||||
| weekDates.push(formattedDate); | |||||
| } | |||||
| setTeamTotalManhoursSpentPeriod(weekDates); | |||||
| setValue(dayjs(firstDayOfWeek)); | |||||
| }; | |||||
| const selectMonthlyPeriodFrom = (r: any) => { | |||||
| const monthDates: any[] = []; | |||||
| const monthPlanData: any[] = []; | |||||
| const monthActualData: any[] = []; | |||||
| const selectFromDate = dayjs(r); | |||||
| for ( | |||||
| let date = selectFromDate.clone(); | |||||
| date.isBefore(totalManHoursMonthlyToValue, "month"); | |||||
| date = date.add(1, "month") | |||||
| ) { | |||||
| monthDates.push(date.format("MM-YYYY")); | |||||
| monthPlanData.push(840); | |||||
| monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); | |||||
| } | |||||
| monthDates.push(totalManHoursMonthlyToValue.format("MM-YYYY")); | |||||
| monthPlanData.push(840); | |||||
| monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); | |||||
| setTeamTotalManhoursSpentPlanData(monthPlanData); | |||||
| setTeamTotalManhoursSpentActualData(monthActualData); | |||||
| setTeamTotalManhoursSpentPeriod(monthDates); | |||||
| }; | |||||
| const selectMonthlyPeriodTo = (r: any) => { | |||||
| const monthDates: any[] = []; | |||||
| const monthPlanData: any[] = []; | |||||
| const monthActualData: any[] = []; | |||||
| const selectToDate = dayjs(r); | |||||
| for ( | |||||
| let date = totalManHoursMonthlyFromValue.clone(); | |||||
| date.isBefore(selectToDate, "month"); | |||||
| date = date.add(1, "month") | |||||
| ) { | |||||
| monthDates.push(date.format("MM-YYYY")); | |||||
| monthPlanData.push(840); | |||||
| monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); | |||||
| } | |||||
| monthDates.push(selectToDate.format("MM-YYYY")); | |||||
| monthPlanData.push(840); | |||||
| monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); | |||||
| setTeamTotalManhoursSpentPlanData(monthPlanData); | |||||
| setTeamTotalManhoursSpentActualData(monthActualData); | |||||
| setTeamTotalManhoursSpentPeriod(monthDates); | |||||
| }; | |||||
| // render | |||||
| return ( | |||||
| <> | |||||
| <Grid item sm> | |||||
| <div style={{ display: "inline-block", width: "40%" }}> | |||||
| <div> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Team Total Manhours Spent" | |||||
| /> | |||||
| <div style={{ display: "inline-block", width: "99%" }}> | |||||
| {/* Weekly / Monthly Button select */} | |||||
| {/* Different: selected style & on click function */} | |||||
| <div className="w-fit align-top mr-5 float-right"> | |||||
| {teamTotalManhoursSpentSelect === "Weekly" && ( | |||||
| <> | |||||
| <button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-l-md w-32"> | |||||
| Weekly | |||||
| </button> | |||||
| <button | |||||
| onClick={() => | |||||
| teamTotalManhoursSpentOnClick("Monthly") | |||||
| } | |||||
| className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-r-md w-32" | |||||
| > | |||||
| Monthly | |||||
| </button> | |||||
| </> | |||||
| )} | |||||
| {teamTotalManhoursSpentSelect === "Monthly" && ( | |||||
| <> | |||||
| <button | |||||
| onClick={() => | |||||
| teamTotalManhoursSpentOnClick("Weekly") | |||||
| } | |||||
| className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-l-md w-32" | |||||
| > | |||||
| Weekly | |||||
| </button> | |||||
| <button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-r-md w-32"> | |||||
| Monthly | |||||
| </button> | |||||
| </> | |||||
| )} | |||||
| </div> | |||||
| {/* Team combo */} | |||||
| <div className="inline-block w-fit mt-2"> | |||||
| {/* Label */} | |||||
| <div className="inline-block ml-6"> | |||||
| <Label className="text-slate-500 font-medium"> | |||||
| Team: | |||||
| </Label> | |||||
| </div> | |||||
| {/* Select Option */} | |||||
| <div className="inline-block ml-1 w-60"> | |||||
| <Select | |||||
| placeholder="All Team" | |||||
| options={teamOptions} | |||||
| isClearable={true} | |||||
| /> | |||||
| </div> | |||||
| {/* Weekly / Monthly Period */} | |||||
| <div className="ml-6 mt-2" style={{ verticalAlign: "top" }}> | |||||
| {teamTotalManhoursSpentSelect === "Weekly" && ( | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DatePicker | |||||
| className="w-72 h-10 align-top" | |||||
| label="Period:" | |||||
| value={value} | |||||
| format="DD-MM-YYYY" | |||||
| onChange={(newValue) => | |||||
| selectWeeklyPeriod(newValue) | |||||
| } | |||||
| showDaysOutsideCurrentMonth | |||||
| displayWeekNumber | |||||
| slots={{ day: Day }} | |||||
| slotProps={{ | |||||
| day: (ownerState) => | |||||
| ({ | |||||
| selectedDay: value, | |||||
| hoveredDay, | |||||
| onPointerEnter: () => | |||||
| setHoveredDay(ownerState.day), | |||||
| onPointerLeave: () => setHoveredDay(null), | |||||
| }) as any, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| )} | |||||
| {teamTotalManhoursSpentSelect === "Monthly" && ( | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DatePicker | |||||
| className="w-40 h-10 align-top" | |||||
| onChange={(newValue) => | |||||
| selectMonthlyPeriodFrom(newValue) | |||||
| } | |||||
| defaultValue={totalManHoursMonthlyFromValue} | |||||
| label={"Form"} | |||||
| views={["month", "year"]} | |||||
| /> | |||||
| <DatePicker | |||||
| className="w-40 h-10 align-top" | |||||
| onChange={(newValue) => | |||||
| selectMonthlyPeriodTo(newValue) | |||||
| } | |||||
| defaultValue={totalManHoursMonthlyToValue} | |||||
| label={"To"} | |||||
| views={["month", "year"]} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| {/* Render Apex Chart */} | |||||
| <ReactApexChart | |||||
| options={options} | |||||
| series={options.series} | |||||
| type="line" | |||||
| height="400" | |||||
| /> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </div> | |||||
| </Grid> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default Bspp; | |||||
| @@ -0,0 +1,10 @@ | |||||
| import Bspp from "./Bspp"; | |||||
| const BsppWrapper: React.FC = async () => { | |||||
| return ( | |||||
| <Bspp></Bspp> | |||||
| ); | |||||
| }; | |||||
| export default BsppWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./BsppWrapper"; | |||||
| @@ -277,6 +277,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined, | projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined, | ||||
| projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined, | projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined, | ||||
| expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined, | expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined, | ||||
| clientId: allCustomers !== undefined ? allCustomers[0].id : undefined, | |||||
| ...defaultInputs, | ...defaultInputs, | ||||
| // manhourPercentageByGrade should have a sensible default | // manhourPercentageByGrade should have a sensible default | ||||
| @@ -430,9 +431,11 @@ const CreateProject: React.FC<Props> = ({ | |||||
| startIcon={<Check />} | startIcon={<Check />} | ||||
| type="submit" | type="submit" | ||||
| disabled={ | disabled={ | ||||
| formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus") === "Deleted" || | |||||
| (!!formProps.getValues("projectActualStart") && | |||||
| !!formProps.getValues("projectActualEnd")) | |||||
| formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus").toLowerCase() === "deleted" || | |||||
| ( | |||||
| // !!formProps.getValues("projectActualStart") && | |||||
| !!(formProps.getValues("projectStatus").toLowerCase() === "completed") | |||||
| ) | |||||
| } | } | ||||
| > | > | ||||
| {isEditMode ? t("Save") : t("Confirm")} | {isEditMode ? t("Save") : t("Confirm")} | ||||
| @@ -118,7 +118,7 @@ const StaffAllocation: React.FC<Props> = ({ | |||||
| }, | }, | ||||
| { label: t("Team"), name: "team" }, | { label: t("Team"), name: "team" }, | ||||
| { label: t("Grade"), name: "grade" }, | { label: t("Grade"), name: "grade" }, | ||||
| { label: t("Staff ID"), name: "id" }, | |||||
| { label: t("Staff ID"), name: "staffId" }, | |||||
| { label: t("Staff Name"), name: "name" }, | { label: t("Staff Name"), name: "name" }, | ||||
| { label: t("Title"), name: "currentPosition" }, | { label: t("Title"), name: "currentPosition" }, | ||||
| ], | ], | ||||
| @@ -80,7 +80,7 @@ const CreateStaff: React.FC<formProps> = ({ combos }) => { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" }) | formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" }) | ||||
| } | } | ||||
| if(!regex_phone.test(data.emergContactPhone)) { | |||||
| if(data.emergContactPhone && !regex_phone.test(data.emergContactPhone)) { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" }) | formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" }) | ||||
| } | } | ||||
| @@ -110,19 +110,11 @@ const CreateStaff: React.FC<formProps> = ({ combos }) => { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" }) | formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" }) | ||||
| } | } | ||||
| if (!data.departmentId) { | |||||
| haveError = true | |||||
| formProps.setError("departmentId", { message: t("Please Enter Department."), type: "required" }) | |||||
| } | |||||
| if (!data.salaryId) { | if (!data.salaryId) { | ||||
| haveError = true | haveError = true | ||||
| formProps.setError("salaryId", { message: t("Please Enter Salary."), type: "required" }) | formProps.setError("salaryId", { message: t("Please Enter Salary."), type: "required" }) | ||||
| } | } | ||||
| if (!data.joinDate) { | |||||
| haveError = true | |||||
| formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" }) | |||||
| } | |||||
| if (data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) { | |||||
| if (data.joinDate &&data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | ||||
| } | } | ||||
| @@ -165,7 +165,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <InputLabel required>{t("Department")}</InputLabel> | |||||
| <InputLabel>{t("Department")}</InputLabel> | |||||
| <Controller | <Controller | ||||
| control={control} | control={control} | ||||
| name="departmentId" | name="departmentId" | ||||
| @@ -377,9 +377,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| label={t("Emergency Contact Name")} | label={t("Emergency Contact Name")} | ||||
| fullWidth | fullWidth | ||||
| required | required | ||||
| {...register("emergContactName", { | |||||
| required: "Emergency Contact Name required!", | |||||
| })} | |||||
| {...register("emergContactName" | |||||
| // , { | |||||
| // required: "Emergency Contact Name required!", | |||||
| // } | |||||
| )} | |||||
| error={Boolean(errors.emergContactName)} | error={Boolean(errors.emergContactName)} | ||||
| helperText={ | helperText={ | ||||
| Boolean(errors.emergContactName) && | Boolean(errors.emergContactName) && | ||||
| @@ -394,9 +396,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| label={t("Emergency Contact Phone")} | label={t("Emergency Contact Phone")} | ||||
| fullWidth | fullWidth | ||||
| required | required | ||||
| {...register("emergContactPhone", { | |||||
| required: "Emergency Contact Phone required!", | |||||
| })} | |||||
| {...register("emergContactPhone" | |||||
| // , { | |||||
| // required: "Emergency Contact Phone required!", | |||||
| // } | |||||
| )} | |||||
| error={Boolean(errors.emergContactPhone)} | error={Boolean(errors.emergContactPhone)} | ||||
| helperText={ | helperText={ | ||||
| Boolean(errors.emergContactPhone) && | Boolean(errors.emergContactPhone) && | ||||
| @@ -421,7 +425,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| }} | }} | ||||
| slotProps={{ | slotProps={{ | ||||
| textField: { | textField: { | ||||
| required: true, | |||||
| // required: true, | |||||
| error: | error: | ||||
| joinDate === "Invalid Date" || Boolean(errors.joinDate), | joinDate === "Invalid Date" || Boolean(errors.joinDate), | ||||
| // value: errors.joinDate?.message, | // value: errors.joinDate?.message, | ||||
| @@ -432,7 +436,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <InputLabel required>{t("Join Position")}</InputLabel> | |||||
| <InputLabel>{t("Join Position")}</InputLabel> | |||||
| <Controller | <Controller | ||||
| control={control} | control={control} | ||||
| name="joinPositionId" | name="joinPositionId" | ||||
| @@ -108,7 +108,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" }) | formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" }) | ||||
| } | } | ||||
| if(!regex_phone.test(data.emergContactPhone)) { | |||||
| if(data.emergContactPhone && !regex_phone.test(data.emergContactPhone)) { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" }) | formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" }) | ||||
| } | } | ||||
| @@ -150,7 +150,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" }) | formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" }) | ||||
| } | } | ||||
| if (data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) { | |||||
| if (data.joinDate &&data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) { | |||||
| haveError = true | haveError = true | ||||
| formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | ||||
| } | } | ||||
| @@ -417,7 +417,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| }} | }} | ||||
| slotProps={{ | slotProps={{ | ||||
| textField: { | textField: { | ||||
| required: true, | |||||
| // required: true, | |||||
| error: | error: | ||||
| joinDate === "Invalid Date" || Boolean(errors.joinDate), | joinDate === "Invalid Date" || Boolean(errors.joinDate), | ||||
| // value: errors.joinDate?.message, | // value: errors.joinDate?.message, | ||||
| @@ -466,7 +466,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| }} | }} | ||||
| slotProps={{ | slotProps={{ | ||||
| textField: { | textField: { | ||||
| error: departDate | |||||
| error: joinDate && departDate | |||||
| ? new Date(joinDate) > new Date(departDate) | ? new Date(joinDate) > new Date(departDate) | ||||
| : false, | : false, | ||||
| }, | }, | ||||
| @@ -54,7 +54,7 @@ const EditStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => { | |||||
| const temp = { | const temp = { | ||||
| id: parseInt(idString), | id: parseInt(idString), | ||||
| ...data, | ...data, | ||||
| emergContactPhone: data.emergContactPhone.toString(), | |||||
| emergContactPhone: data.emergContactPhone ? data.emergContactPhone.toString() : "", | |||||
| phone1: data.phone1.toString(), | phone1: data.phone1.toString(), | ||||
| phone2: data.phone1.toString(), | phone2: data.phone1.toString(), | ||||
| joinDate: formatJoinDate, | joinDate: formatJoinDate, | ||||
| @@ -175,7 +175,7 @@ const EditTeam: React.FC<Props> = async ({ staff, teamInfo }) => { | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| startIcon={<Close />} | startIcon={<Close />} | ||||
| // onClick={handleCancel} | |||||
| onClick={() => router.back()} | |||||
| > | > | ||||
| {t("Cancel")} | {t("Cancel")} | ||||
| </Button> | </Button> | ||||
| @@ -50,6 +50,7 @@ interface Props { | |||||
| } | } | ||||
| const EditUser: React.FC<Props> = async ({ user, rules, auths }) => { | const EditUser: React.FC<Props> = async ({ user, rules, auths }) => { | ||||
| console.log(user) | |||||
| const { t } = useTranslation("user"); | const { t } = useTranslation("user"); | ||||
| const formProps = useForm<UserInputs>(); | const formProps = useForm<UserInputs>(); | ||||
| const searchParams = useSearchParams(); | const searchParams = useSearchParams(); | ||||
| @@ -79,7 +80,7 @@ const EditUser: React.FC<Props> = async ({ user, rules, auths }) => { | |||||
| console.log(addAuthIds); | console.log(addAuthIds); | ||||
| try { | try { | ||||
| formProps.reset({ | formProps.reset({ | ||||
| name: user.username, | |||||
| username: user.username, | |||||
| email: user.email, | email: user.email, | ||||
| addAuthIds: addAuthIds, | addAuthIds: addAuthIds, | ||||
| removeAuthIds: [], | removeAuthIds: [], | ||||
| @@ -145,7 +146,8 @@ const EditUser: React.FC<Props> = async ({ user, rules, auths }) => { | |||||
| } | } | ||||
| } | } | ||||
| const userData = { | const userData = { | ||||
| name: data.name, | |||||
| username: data.username, | |||||
| name: user.name, | |||||
| locked: false, | locked: false, | ||||
| addAuthIds: data.addAuthIds || [], | addAuthIds: data.addAuthIds || [], | ||||
| removeAuthIds: data.removeAuthIds || [], | removeAuthIds: data.removeAuthIds || [], | ||||
| @@ -15,14 +15,16 @@ interface SubComponents { | |||||
| } | } | ||||
| const EditUserWrapper: React.FC<searchParamsProps> & SubComponents = async ({ | const EditUserWrapper: React.FC<searchParamsProps> & SubComponents = async ({ | ||||
| searchParams | |||||
| searchParams, | |||||
| }) => { | }) => { | ||||
| const id = parseInt(searchParams.id as string) | |||||
| const pwRule = await fetchPwRules() | |||||
| const user = await fetchUserDetails(id); | |||||
| const auths = await fetchAuth("user", id); | |||||
| const id = parseInt(searchParams.id as string); | |||||
| const [pwRule, user, auths] = await Promise.all([ | |||||
| fetchPwRules(), | |||||
| fetchUserDetails(id), | |||||
| fetchAuth("user", id), | |||||
| ]); | |||||
| return <EditUser user={user.data} rules={pwRule} auths={auths.records}/> | |||||
| return <EditUser user={user.data} rules={pwRule} auths={auths.records} />; | |||||
| }; | }; | ||||
| EditUserWrapper.Loading = EditUserLoading; | EditUserWrapper.Loading = EditUserLoading; | ||||
| @@ -34,10 +34,10 @@ const UserDetail: React.FC = () => { | |||||
| <TextField | <TextField | ||||
| label={t("username")} | label={t("username")} | ||||
| fullWidth | fullWidth | ||||
| {...register("name", { | |||||
| {...register("username", { | |||||
| required: "username required!", | required: "username required!", | ||||
| })} | })} | ||||
| error={Boolean(errors.name)} | |||||
| error={Boolean(errors.username)} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
| // import { TeamResult } from "@/app/api/team"; | // import { TeamResult } from "@/app/api/team"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -13,22 +13,22 @@ import { | |||||
| useForm, | useForm, | ||||
| useFormContext, | useFormContext, | ||||
| } from "react-hook-form"; | } from "react-hook-form"; | ||||
| import { Check, Close, Error } from "@mui/icons-material"; | |||||
| import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; | |||||
| import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
| import { CreateGroupInputs, auth, fetchAuth, saveGroup } from "@/app/api/group/actions"; | import { CreateGroupInputs, auth, fetchAuth, saveGroup } from "@/app/api/group/actions"; | ||||
| import { UserGroupResult } from "@/app/api/group"; | |||||
| import { IndivUserGroup, UserGroupResult } from "@/app/api/group"; | |||||
| import { UserResult } from "@/app/api/user"; | import { UserResult } from "@/app/api/user"; | ||||
| import GroupInfo from "./GroupInfo"; | import GroupInfo from "./GroupInfo"; | ||||
| import AuthorityAllocation from "./AuthorityAllocation"; | import AuthorityAllocation from "./AuthorityAllocation"; | ||||
| import UserAllocation from "./UserAllocation"; | import UserAllocation from "./UserAllocation"; | ||||
| interface Props { | interface Props { | ||||
| groups: UserGroupResult[]; | |||||
| // auths: auth[]; | |||||
| // groups: UserGroupResult[]; | |||||
| auths: auth[]; | |||||
| users: UserResult[]; | users: UserResult[]; | ||||
| group: IndivUserGroup | |||||
| } | } | ||||
| const EditUserGroup: React.FC<Props> = ({ groups, users }) => { | |||||
| // console.log(users) | |||||
| const EditUserGroup: React.FC<Props> = ({ users, auths, group }) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
| const formProps = useForm<CreateGroupInputs>(); | const formProps = useForm<CreateGroupInputs>(); | ||||
| @@ -36,7 +36,6 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => { | |||||
| const id = parseInt(searchParams.get("id") || "0"); | const id = parseInt(searchParams.get("id") || "0"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const [auths, setAuths] = useState<auth[]>(); | |||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| @@ -79,22 +78,20 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => { | |||||
| }, | }, | ||||
| [router] | [router] | ||||
| ); | ); | ||||
| const resetGroup = React.useCallback(() => { | |||||
| formProps.reset({ | |||||
| name: group.data.name, | |||||
| description: group.data.description, | |||||
| addAuthIds: group.authIds, | |||||
| addUserIds: group.userIds, | |||||
| }) | |||||
| }, [group, users]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const thisGroup = groups.filter((item) => item.id === id)[0]; | |||||
| const addUserIds = users.filter((item) => item.groupId === id).map((data) => data.id) | |||||
| let addAuthIds: number[] = [] | |||||
| fetchAuth("group", id).then((data) => { | |||||
| setAuths(data.records) | |||||
| addAuthIds = data.records.filter((data) => data.v === 1).map((data) => data.id).sort((a, b) => a - b); | |||||
| formProps.reset({ | |||||
| name: thisGroup.name, | |||||
| description: thisGroup.description, | |||||
| addAuthIds: addAuthIds, | |||||
| addUserIds: addUserIds, | |||||
| }); | |||||
| }); | |||||
| // console.log(auths) | |||||
| }, [groups, users]); | |||||
| resetGroup() | |||||
| }, []); | |||||
| // }, [group, users]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -140,10 +137,17 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => { | |||||
| {tabIndex === 1 && <AuthorityAllocation auth={auths!!}/>} | {tabIndex === 1 && <AuthorityAllocation auth={auths!!}/>} | ||||
| {tabIndex === 2 && <UserAllocation users={users!!} />} | {tabIndex === 2 && <UserAllocation users={users!!} />} | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | |||||
| variant="text" | |||||
| startIcon={<RestartAlt />} | |||||
| onClick={resetGroup} | |||||
| > | |||||
| {t("Reset")} | |||||
| </Button> | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| startIcon={<Close />} | startIcon={<Close />} | ||||
| // onClick={handleCancel} | |||||
| onClick={() => router.back()} | |||||
| > | > | ||||
| {t("Cancel")} | {t("Cancel")} | ||||
| </Button> | </Button> | ||||
| @@ -1,24 +1,31 @@ | |||||
| import React from "react"; | import React from "react"; | ||||
| import EditUserGroup from "./EditUserGroup"; | import EditUserGroup from "./EditUserGroup"; | ||||
| import EditUserGroupLoading from "./EditUserGroupLoading"; | import EditUserGroupLoading from "./EditUserGroupLoading"; | ||||
| import { fetchGroup } from "@/app/api/group"; | |||||
| import { fetchGroup, fetchIndivGroup } from "@/app/api/group"; | |||||
| import { fetchUser } from "@/app/api/user"; | import { fetchUser } from "@/app/api/user"; | ||||
| import { fetchAuth } from "@/app/api/group/actions"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof EditUserGroupLoading; | Loading: typeof EditUserGroupLoading; | ||||
| } | } | ||||
| const EditUserGroupWrapper: React.FC & SubComponents = async () => { | |||||
| interface Props { | |||||
| id: number | |||||
| } | |||||
| const EditUserGroupWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||||
| const [ | const [ | ||||
| groups, | |||||
| group, | |||||
| users, | users, | ||||
| auths, | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchGroup(), | |||||
| fetchIndivGroup(id), | |||||
| fetchUser(), | fetchUser(), | ||||
| fetchAuth("group", id), | |||||
| ]); | ]); | ||||
| return <EditUserGroup groups={groups.records} users={users}/>; | |||||
| return <EditUserGroup users={users} auths={auths.records} group={group}/>; | |||||
| }; | }; | ||||
| EditUserGroupWrapper.Loading = EditUserGroupLoading; | EditUserGroupWrapper.Loading = EditUserGroupLoading; | ||||
| @@ -43,6 +43,7 @@ const GroupInfo: React.FC = () => { | |||||
| <TextField | <TextField | ||||
| label={t("Group Name")} | label={t("Group Name")} | ||||
| fullWidth | fullWidth | ||||
| rows={4} | |||||
| {...register("name", { | {...register("name", { | ||||
| required: true, | required: true, | ||||
| })} | })} | ||||
| @@ -49,11 +49,17 @@ const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => { | |||||
| formType={"download"} | formType={"download"} | ||||
| criteria={searchCriteria} | criteria={searchCriteria} | ||||
| onSearch={async (query: any) => { | onSearch={async (query: any) => { | ||||
| console.log(query); | |||||
| const index = staffCombo.findIndex((staff) => staff === query.staff); | const index = staffCombo.findIndex((staff) => staff === query.staff); | ||||
| const response = await fetchMonthlyWorkHoursReport({ | |||||
| let postData = { | |||||
| id: staffs[index].id, | id: staffs[index].id, | ||||
| yearMonth: query.date, | |||||
| }); | |||||
| yearMonth: dayjs().format("YYYY-MM").toString(), | |||||
| }; | |||||
| console.log(query.date.length > 0) | |||||
| if (query.date.length > 0) { | |||||
| postData.yearMonth = query.date | |||||
| } | |||||
| const response = await fetchMonthlyWorkHoursReport(postData); | |||||
| if (response) { | if (response) { | ||||
| downloadFile( | downloadFile( | ||||
| new Uint8Array(response.blobValue), | new Uint8Array(response.blobValue), | ||||
| @@ -63,7 +63,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
| { | { | ||||
| icon: <WorkHistory />, | icon: <WorkHistory />, | ||||
| label: "User Workspace", | label: "User Workspace", | ||||
| path: "/home", | |||||
| path: "/home", | |||||
| showOnMobile: true, | showOnMobile: true, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -71,6 +71,11 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
| label: "Dashboard", | label: "Dashboard", | ||||
| path: "", | path: "", | ||||
| children: [ | children: [ | ||||
| { | |||||
| icon: <WorkHistory />, | |||||
| label: "BSPP", | |||||
| path: "/dashboard/bspp", | |||||
| }, | |||||
| { | { | ||||
| icon: <SummarizeIcon />, | icon: <SummarizeIcon />, | ||||
| label: "Financial Summary", | label: "Financial Summary", | ||||