| @@ -14,7 +14,7 @@ export const getPublicHolidaysForNYears = (years: number = 1, currYr?: number) = | |||||
| .flatMap((_, index) => { | .flatMap((_, index) => { | ||||
| const currentYear = currYr ?? new Date().getFullYear(); | const currentYear = currYr ?? new Date().getFullYear(); | ||||
| const holidays = hd.getHolidays(currentYear - index); | const holidays = hd.getHolidays(currentYear - index); | ||||
| console.log(holidays) | |||||
| // console.log(holidays) | |||||
| return holidays.map((ele) => { | return holidays.map((ele) => { | ||||
| const tempDay = new Date(ele.date); | const tempDay = new Date(ele.date); | ||||
| const tempYear = tempDay.getFullYear(); | const tempYear = tempDay.getFullYear(); | ||||
| @@ -11,9 +11,9 @@ import { | |||||
| Typography, | Typography, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import union from "lodash/union"; | import union from "lodash/union"; | ||||
| import { useCallback, useMemo } from "react"; | |||||
| import { useCallback, useEffect, useMemo } from "react"; | |||||
| import dayjs, { Dayjs } from "dayjs"; | import dayjs, { Dayjs } from "dayjs"; | ||||
| import { getHolidayForDate } from "@/app/utils/holidayUtils"; | |||||
| import { getHolidayForDate, getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||||
| import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
| import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; | import { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -39,6 +39,14 @@ const MonthlySummary: React.FC<Props> = ({ | |||||
| i18n: { language }, | i18n: { language }, | ||||
| } = useTranslation("home"); | } = useTranslation("home"); | ||||
| // calendar related | |||||
| const holidays = useMemo(() => { | |||||
| const holidays = getPublicHolidaysForNYears(1, currentMonth.year()).map(holiday => holiday.date) | |||||
| return holidays.filter(date => { | |||||
| return currentMonth.isSame(dayjs(date), "month") | |||||
| }) | |||||
| }, [currentMonth]); | |||||
| const timesheetForCurrentMonth = useMemo(() => { | const timesheetForCurrentMonth = useMemo(() => { | ||||
| return pickBy(timesheet, (_, date) => { | return pickBy(timesheet, (_, date) => { | ||||
| return currentMonth.isSame(dayjs(date), "month"); | return currentMonth.isSame(dayjs(date), "month"); | ||||
| @@ -55,8 +63,9 @@ const MonthlySummary: React.FC<Props> = ({ | |||||
| return union( | return union( | ||||
| Object.keys(timesheetForCurrentMonth), | Object.keys(timesheetForCurrentMonth), | ||||
| Object.keys(leavesForCurrentMonth), | Object.keys(leavesForCurrentMonth), | ||||
| holidays | |||||
| ); | ); | ||||
| }, [timesheetForCurrentMonth, leavesForCurrentMonth]).sort(); | |||||
| }, [timesheetForCurrentMonth, leavesForCurrentMonth, holidays]).sort(); | |||||
| const makeSelectDate = useCallback( | const makeSelectDate = useCallback( | ||||
| (date: string) => () => { | (date: string) => () => { | ||||
| @@ -65,6 +74,11 @@ const MonthlySummary: React.FC<Props> = ({ | |||||
| [onDateSelect], | [onDateSelect], | ||||
| ); | ); | ||||
| useEffect(()=> { | |||||
| console.log(holidays) | |||||
| console.log(timesheetForCurrentMonth) | |||||
| },[currentMonth, timesheetForCurrentMonth]) | |||||
| return ( | return ( | ||||
| <Stack | <Stack | ||||
| gap={2} | gap={2} | ||||
| @@ -25,6 +25,7 @@ dayjs.tz.guess(); | |||||
| export interface Props { | export interface Props { | ||||
| timesheet: RecordTimesheetInput; | timesheet: RecordTimesheetInput; | ||||
| leaves: RecordLeaveInput; | leaves: RecordLeaveInput; | ||||
| publicHolidays: string[]; | |||||
| onDateSelect: (date: string) => void; | onDateSelect: (date: string) => void; | ||||
| onMonthChange: (day: Dayjs) => void; | onMonthChange: (day: Dayjs) => void; | ||||
| } | } | ||||
| @@ -32,6 +33,7 @@ export interface Props { | |||||
| const getColor = ( | const getColor = ( | ||||
| hasTimeInput: boolean, | hasTimeInput: boolean, | ||||
| hasLeave: boolean, | hasLeave: boolean, | ||||
| isPublicHoliday: boolean, | |||||
| ): string | undefined => { | ): string | undefined => { | ||||
| if (hasTimeInput && hasLeave) { | if (hasTimeInput && hasLeave) { | ||||
| return "success.light"; | return "success.light"; | ||||
| @@ -39,7 +41,9 @@ const getColor = ( | |||||
| return "info.light"; | return "info.light"; | ||||
| } else if (hasLeave) { | } else if (hasLeave) { | ||||
| return "warning.light"; | return "warning.light"; | ||||
| } else { | |||||
| } else if (isPublicHoliday){ | |||||
| return "error.light"; | |||||
| }else { | |||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -47,6 +51,7 @@ const getColor = ( | |||||
| const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | ||||
| timesheet, | timesheet, | ||||
| leaves, | leaves, | ||||
| publicHolidays, | |||||
| ...pickerProps | ...pickerProps | ||||
| }) => { | }) => { | ||||
| const timesheetDays = Object.keys(timesheet); | const timesheetDays = Object.keys(timesheet); | ||||
| @@ -60,11 +65,15 @@ const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | |||||
| dayjs(day).isSame(pickerProps.day, "day"), | dayjs(day).isSame(pickerProps.day, "day"), | ||||
| ); | ); | ||||
| const isPublicHoliday = publicHolidays.some((day) => | |||||
| dayjs(day).isSame(pickerProps.day, "day"), | |||||
| ); | |||||
| return ( | return ( | ||||
| <PickersDay | <PickersDay | ||||
| {...pickerProps} | {...pickerProps} | ||||
| disabled={!(hasTimesheetInput || hasLeaveInput)} | disabled={!(hasTimesheetInput || hasLeaveInput)} | ||||
| sx={{ backgroundColor: getColor(hasTimesheetInput, hasLeaveInput) }} | |||||
| sx={{ backgroundColor: getColor(hasTimesheetInput, hasLeaveInput, isPublicHoliday) }} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -72,6 +81,7 @@ const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | |||||
| const PastEntryCalendar: React.FC<Props> = ({ | const PastEntryCalendar: React.FC<Props> = ({ | ||||
| timesheet, | timesheet, | ||||
| leaves, | leaves, | ||||
| publicHolidays, | |||||
| onDateSelect, | onDateSelect, | ||||
| onMonthChange, | onMonthChange, | ||||
| }) => { | }) => { | ||||
| @@ -98,6 +108,7 @@ const PastEntryCalendar: React.FC<Props> = ({ | |||||
| day: { | day: { | ||||
| timesheet, | timesheet, | ||||
| leaves, | leaves, | ||||
| publicHolidays, | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| } as any, | } as any, | ||||
| }} | }} | ||||
| @@ -12,7 +12,7 @@ import { | |||||
| import PastEntryCalendar, { | import PastEntryCalendar, { | ||||
| Props as PastEntryCalendarProps, | Props as PastEntryCalendarProps, | ||||
| } from "./PastEntryCalendar"; | } from "./PastEntryCalendar"; | ||||
| import { useCallback, useState } from "react"; | |||||
| import { useCallback, useState, useMemo } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { ArrowBack } from "@mui/icons-material"; | import { ArrowBack } from "@mui/icons-material"; | ||||
| import PastEntryList from "./PastEntryList"; | import PastEntryList from "./PastEntryList"; | ||||
| @@ -23,9 +23,10 @@ import FullscreenModal from "../FullscreenModal"; | |||||
| import MonthlySummary from "./MonthlySummary"; | import MonthlySummary from "./MonthlySummary"; | ||||
| import { HolidaysResult } from "@/app/api/holidays"; | import { HolidaysResult } from "@/app/api/holidays"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||||
| interface Props | interface Props | ||||
| extends Omit<PastEntryCalendarProps, "onDateSelect" | "onMonthChange"> { | |||||
| extends Omit<PastEntryCalendarProps, "onDateSelect" | "onMonthChange" | "publicHolidays"> { | |||||
| open: boolean; | open: boolean; | ||||
| handleClose: () => void; | handleClose: () => void; | ||||
| leaveTypes: LeaveType[]; | leaveTypes: LeaveType[]; | ||||
| @@ -60,6 +61,13 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||||
| handleClose(); | handleClose(); | ||||
| }, [handleClose]); | }, [handleClose]); | ||||
| const publicHolidays = useMemo(() => { | |||||
| const holidays = getPublicHolidaysForNYears(1, currentMonth.year()).map(holiday => holiday.date) | |||||
| return holidays.filter(date => { | |||||
| return currentMonth.isSame(dayjs(date), "month") | |||||
| }) | |||||
| }, [currentMonth]); | |||||
| const content = ( | const content = ( | ||||
| <Box sx={{ display: "flex", flexDirection: { xs: "column", sm: "row" } }}> | <Box sx={{ display: "flex", flexDirection: { xs: "column", sm: "row" } }}> | ||||
| <Box> | <Box> | ||||
| @@ -80,10 +88,17 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||||
| {t("Has both timesheet and leave entry")} | {t("Has both timesheet and leave entry")} | ||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| <Box display="flex" alignItems="center" gap={1}> | |||||
| <Indicator sx={{ backgroundColor: "error.light" }} /> | |||||
| <Typography variant="caption"> | |||||
| {t("Public Holiday")} | |||||
| </Typography> | |||||
| </Box> | |||||
| </Stack> | </Stack> | ||||
| <PastEntryCalendar | <PastEntryCalendar | ||||
| timesheet={timesheet} | timesheet={timesheet} | ||||
| leaves={leaves} | leaves={leaves} | ||||
| publicHolidays={publicHolidays} | |||||
| onDateSelect={setSelectedDate} | onDateSelect={setSelectedDate} | ||||
| onMonthChange={setMonthChange} | onMonthChange={setMonthChange} | ||||
| /> | /> | ||||
| @@ -307,7 +307,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| > | > | ||||
| {"(k) " + t("Projected Cost Performance Index") + " (CPI)"} | {"(k) " + t("Projected Cost Performance Index") + " (CPI)"} | ||||
| </div> | </div> | ||||
| {Number(ProjectedCPI) < 1 && ( | |||||
| {Number(ProjectedCPI) < 1.2 && ( | |||||
| <> | <> | ||||
| <div | <div | ||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| @@ -320,7 +320,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| </div> | </div> | ||||
| </> | </> | ||||
| )} | )} | ||||
| {Number(ProjectedCPI) >= 1 && ( | |||||
| {Number(ProjectedCPI) >= 1.2 && ( | |||||
| <> | <> | ||||
| <div | <div | ||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| @@ -8,6 +8,7 @@ import dayjs from 'dayjs'; | |||||
| import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | ||||
| import { SumOfByClient, SumOfByTeam, sumUpByClient, sumUpByTeam } from './gptFn'; | import { SumOfByClient, SumOfByTeam, sumUpByClient, sumUpByTeam } from './gptFn'; | ||||
| import FinancialStatusByProject from './FinnancialStatusByProject'; | import FinancialStatusByProject from './FinnancialStatusByProject'; | ||||
| import { generateYearRanges } from './Util'; | |||||
| interface Props { | interface Props { | ||||
| _teamId: number, | _teamId: number, | ||||
| @@ -17,6 +18,7 @@ interface Props { | |||||
| type InputDate = { | type InputDate = { | ||||
| startDate: string; | startDate: string; | ||||
| endDate: string; | endDate: string; | ||||
| label?: string; | |||||
| } | } | ||||
| type DateParams = { | type DateParams = { | ||||
| @@ -116,16 +118,28 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| // return list | // return list | ||||
| // }, []) | // }, []) | ||||
| const comboList: string[] = useMemo(() => { | |||||
| const list = ["All"]; | |||||
| for (let i = 1; i < lengthOfCombo - 1; i++) { | |||||
| const yearRange = `${currFinancialYear - i} - ${currFinancialYear - i + 1}`; | |||||
| const label = i === 1 ? `${yearRange} ${t("(current year)")}` : yearRange; | |||||
| list.push(label); | |||||
| } | |||||
| const oldestYear = currFinancialYear - (lengthOfCombo - 2); | |||||
| list.push(`< ${oldestYear}`); | |||||
| return list; | |||||
| const comboList: InputDate[] = useMemo(() => { | |||||
| const list = [{startDate: "", endDate: "", label: "All"}]; | |||||
| const curMonth = new Date().getMonth() + 1 | |||||
| // for (let i = 1; i < lengthOfCombo - 1; i++) { | |||||
| // const yearRange = `${currFinancialYear - i} - ${currFinancialYear - i + 1}`; | |||||
| // const label = i === 1 ? `${yearRange} ${t("(current year)")}` : yearRange; | |||||
| // list.push(label); | |||||
| // } | |||||
| // const oldestYear = currFinancialYear - (lengthOfCombo - 2); | |||||
| // list.push(`< ${oldestYear}`); | |||||
| // for (let j = 0; j < lengthOfCombo - 3; j++) { | |||||
| // const yearRange = `${currFinancialYear + j} - ${currFinancialYear + j + 1}`; | |||||
| // const label = yearRange; | |||||
| // list.push(label); | |||||
| // } | |||||
| // const latestYear = currFinancialYear + (lengthOfCombo - 2); | |||||
| // list.push(`< ${latestYear}`); | |||||
| const tempList = generateYearRanges(3, curMonth) | |||||
| const combinedList = [...list, ...tempList] | |||||
| return combinedList; | |||||
| }, [currFinancialYear, lengthOfCombo, t]); | }, [currFinancialYear, lengthOfCombo, t]); | ||||
| const futureList: string[] = useMemo(() => { | const futureList: string[] = useMemo(() => { | ||||
| @@ -165,21 +179,22 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| }, []); | }, []); | ||||
| const handleFilter = useCallback(async (value: number) => { | const handleFilter = useCallback(async (value: number) => { | ||||
| if (isFuture){ | |||||
| // if (isFuture){ | |||||
| setPeriod(value) | setPeriod(value) | ||||
| console.log(value) | console.log(value) | ||||
| var _startDate = dateMap[value as keyof DateParams].startDate | |||||
| var _endDate = dateMap[value as keyof DateParams].endDate | |||||
| var _startDate = comboList[value].startDate | |||||
| var _endDate = comboList[value].endDate | |||||
| console.log(_startDate) | console.log(_startDate) | ||||
| console.log(_endDate) | console.log(_endDate) | ||||
| }else{ | |||||
| setPeriod(value) | |||||
| console.log(value) | |||||
| var _startDate = futureDateMap[value as keyof DateParams].startDate | |||||
| var _endDate = futureDateMap[value as keyof DateParams].endDate | |||||
| console.log(_startDate) | |||||
| console.log(_endDate) | |||||
| } | |||||
| // } | |||||
| // else{ | |||||
| // setPeriod(value) | |||||
| // console.log(value) | |||||
| // var _startDate = futureDateMap[value as keyof DateParams].startDate | |||||
| // var _endDate = futureDateMap[value as keyof DateParams].endDate | |||||
| // console.log(_startDate) | |||||
| // console.log(_endDate) | |||||
| // } | |||||
| await fetchFinancialSummaryByProject(_endDate, _startDate) | await fetchFinancialSummaryByProject(_endDate, _startDate) | ||||
| @@ -212,21 +227,21 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| onChange={(e) => handleFilter(Number(e.target.value))} | onChange={(e) => handleFilter(Number(e.target.value))} | ||||
| > | > | ||||
| { | { | ||||
| isFuture | |||||
| ? | |||||
| comboList.map((str, i) => ( | |||||
| <MenuItem key={i} value={i}>{str}</MenuItem> | |||||
| // isFuture | |||||
| // ? | |||||
| comboList.map((item, i) => ( | |||||
| <MenuItem key={i} value={i}>{item.label}</MenuItem> | |||||
| )) | )) | ||||
| : | |||||
| Object.entries(futureDateMap).map(([year, dates], index) => { | |||||
| const isLastItem = index === Object.keys(futureDateMap).length - 1; | |||||
| const str = isLastItem ? `> ${dates.startDate.slice(0,4)}` : `${dates.startDate.slice(0,4)} - ${dates.endDate.slice(0,4)}`; | |||||
| return <MenuItem key={year} value={year}>{str}</MenuItem>; | |||||
| }) | |||||
| // : | |||||
| // Object.entries(futureDateMap).map(([year, dates], index) => { | |||||
| // const isLastItem = index === Object.keys(futureDateMap).length - 1; | |||||
| // const str = isLastItem ? `> ${dates.startDate.slice(0,4)}` : `${dates.startDate.slice(0,4)} - ${dates.endDate.slice(0,4)}`; | |||||
| // return <MenuItem key={year} value={year}>{str}</MenuItem>; | |||||
| // }) | |||||
| } | } | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <FormControlLabel | |||||
| {/* <FormControlLabel | |||||
| control={ | control={ | ||||
| <Switch | <Switch | ||||
| checked={!isFuture} | checked={!isFuture} | ||||
| @@ -236,7 +251,7 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| } | } | ||||
| label={isFuture ? t("Past Years") : t("Future Years")} | label={isFuture ? t("Past Years") : t("Future Years")} | ||||
| /> | |||||
| /> */} | |||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| @@ -0,0 +1,71 @@ | |||||
| type InputDate = { | |||||
| startDate: string; | |||||
| endDate: string; | |||||
| label?: string; | |||||
| } | |||||
| /** | |||||
| * Generates a list of year ranges based on the number of past and future years specified. | |||||
| * @param numberOfYears The number of past and future years to include separately. | |||||
| * @returns An array of objects with start year, end year, and label. | |||||
| */ | |||||
| export function generateYearRanges(numberOfYears: number, month: number): InputDate[] { | |||||
| const startDate = "10-01" //start date of each financial year | |||||
| const endDate = "09-30" // end date of each financial year | |||||
| // Get the base year | |||||
| const baseYear = new Date().getFullYear(); | |||||
| // Determine the current year based on the month | |||||
| let currentYear: number; | |||||
| if (month < 10) { // October is month 10 | |||||
| currentYear = baseYear - 1; | |||||
| } else { | |||||
| currentYear = baseYear; | |||||
| } | |||||
| // Initialize the result array | |||||
| const yearRanges: InputDate[] = []; | |||||
| // Add past years beyond numberOfYears as one group | |||||
| if (numberOfYears > 0) { | |||||
| yearRanges.push({ | |||||
| startDate: "", | |||||
| endDate: `${(currentYear - numberOfYears)}-${endDate}`, | |||||
| label: `< ${currentYear - numberOfYears}` | |||||
| }); | |||||
| } | |||||
| // Add individual past years | |||||
| for (let i = numberOfYears - 1; i >= 0; i--) { | |||||
| const startYear = currentYear - i - 1; | |||||
| const endYear = currentYear - i; | |||||
| yearRanges.push({ startDate: `${startYear}-${startDate}`, endDate: `${endYear}-${endDate}`, label: `${startYear} - ${endYear}` }); | |||||
| } | |||||
| // Add current year | |||||
| yearRanges.push({ | |||||
| startDate: `${currentYear}-${startDate}`, | |||||
| endDate: `${(currentYear + 1)}-${endDate}`, | |||||
| label: `${currentYear} - ${currentYear + 1} (current year)` | |||||
| }); | |||||
| // Add individual future years | |||||
| for (let i = 0; i < numberOfYears; i++) { | |||||
| const startYear = currentYear + i + 1; | |||||
| const endYear = currentYear + i + 2; | |||||
| yearRanges.push({ startDate: `${startYear}-${startDate}`, endDate: `${endYear}-${endDate}`, label: `${startYear} - ${endYear}` }); | |||||
| } | |||||
| // Add future years beyond numberOfYears as one group | |||||
| if (numberOfYears > 0) { | |||||
| yearRanges.push({ | |||||
| startDate: `${(currentYear + numberOfYears + 1)}-${startDate}`, | |||||
| endDate: "", | |||||
| label: `> ${currentYear + numberOfYears + 1}` | |||||
| }); | |||||
| } | |||||
| return yearRanges; | |||||
| } | |||||