| @@ -14,7 +14,7 @@ export const getPublicHolidaysForNYears = (years: number = 1, currYr?: number) = | |||
| .flatMap((_, index) => { | |||
| const currentYear = currYr ?? new Date().getFullYear(); | |||
| const holidays = hd.getHolidays(currentYear - index); | |||
| console.log(holidays) | |||
| // console.log(holidays) | |||
| return holidays.map((ele) => { | |||
| const tempDay = new Date(ele.date); | |||
| const tempYear = tempDay.getFullYear(); | |||
| @@ -11,9 +11,9 @@ import { | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import union from "lodash/union"; | |||
| import { useCallback, useMemo } from "react"; | |||
| import { useCallback, useEffect, useMemo } from "react"; | |||
| 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 { manhourFormatter, shortDateFormatter } from "@/app/utils/formatUtil"; | |||
| import { useTranslation } from "react-i18next"; | |||
| @@ -39,6 +39,14 @@ const MonthlySummary: React.FC<Props> = ({ | |||
| i18n: { language }, | |||
| } = 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(() => { | |||
| return pickBy(timesheet, (_, date) => { | |||
| return currentMonth.isSame(dayjs(date), "month"); | |||
| @@ -55,8 +63,9 @@ const MonthlySummary: React.FC<Props> = ({ | |||
| return union( | |||
| Object.keys(timesheetForCurrentMonth), | |||
| Object.keys(leavesForCurrentMonth), | |||
| holidays | |||
| ); | |||
| }, [timesheetForCurrentMonth, leavesForCurrentMonth]).sort(); | |||
| }, [timesheetForCurrentMonth, leavesForCurrentMonth, holidays]).sort(); | |||
| const makeSelectDate = useCallback( | |||
| (date: string) => () => { | |||
| @@ -65,6 +74,11 @@ const MonthlySummary: React.FC<Props> = ({ | |||
| [onDateSelect], | |||
| ); | |||
| useEffect(()=> { | |||
| console.log(holidays) | |||
| console.log(timesheetForCurrentMonth) | |||
| },[currentMonth, timesheetForCurrentMonth]) | |||
| return ( | |||
| <Stack | |||
| gap={2} | |||
| @@ -25,6 +25,7 @@ dayjs.tz.guess(); | |||
| export interface Props { | |||
| timesheet: RecordTimesheetInput; | |||
| leaves: RecordLeaveInput; | |||
| publicHolidays: string[]; | |||
| onDateSelect: (date: string) => void; | |||
| onMonthChange: (day: Dayjs) => void; | |||
| } | |||
| @@ -32,6 +33,7 @@ export interface Props { | |||
| const getColor = ( | |||
| hasTimeInput: boolean, | |||
| hasLeave: boolean, | |||
| isPublicHoliday: boolean, | |||
| ): string | undefined => { | |||
| if (hasTimeInput && hasLeave) { | |||
| return "success.light"; | |||
| @@ -39,7 +41,9 @@ const getColor = ( | |||
| return "info.light"; | |||
| } else if (hasLeave) { | |||
| return "warning.light"; | |||
| } else { | |||
| } else if (isPublicHoliday){ | |||
| return "error.light"; | |||
| }else { | |||
| return undefined; | |||
| } | |||
| }; | |||
| @@ -47,6 +51,7 @@ const getColor = ( | |||
| const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | |||
| timesheet, | |||
| leaves, | |||
| publicHolidays, | |||
| ...pickerProps | |||
| }) => { | |||
| const timesheetDays = Object.keys(timesheet); | |||
| @@ -60,11 +65,15 @@ const EntryDay: React.FC<PickersDayProps<Dayjs> & Props> = ({ | |||
| dayjs(day).isSame(pickerProps.day, "day"), | |||
| ); | |||
| const isPublicHoliday = publicHolidays.some((day) => | |||
| dayjs(day).isSame(pickerProps.day, "day"), | |||
| ); | |||
| return ( | |||
| <PickersDay | |||
| {...pickerProps} | |||
| 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> = ({ | |||
| timesheet, | |||
| leaves, | |||
| publicHolidays, | |||
| onDateSelect, | |||
| onMonthChange, | |||
| }) => { | |||
| @@ -98,6 +108,7 @@ const PastEntryCalendar: React.FC<Props> = ({ | |||
| day: { | |||
| timesheet, | |||
| leaves, | |||
| publicHolidays, | |||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
| } as any, | |||
| }} | |||
| @@ -12,7 +12,7 @@ import { | |||
| import PastEntryCalendar, { | |||
| Props as PastEntryCalendarProps, | |||
| } from "./PastEntryCalendar"; | |||
| import { useCallback, useState } from "react"; | |||
| import { useCallback, useState, useMemo } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { ArrowBack } from "@mui/icons-material"; | |||
| import PastEntryList from "./PastEntryList"; | |||
| @@ -23,9 +23,10 @@ import FullscreenModal from "../FullscreenModal"; | |||
| import MonthlySummary from "./MonthlySummary"; | |||
| import { HolidaysResult } from "@/app/api/holidays"; | |||
| import dayjs from "dayjs"; | |||
| import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||
| interface Props | |||
| extends Omit<PastEntryCalendarProps, "onDateSelect" | "onMonthChange"> { | |||
| extends Omit<PastEntryCalendarProps, "onDateSelect" | "onMonthChange" | "publicHolidays"> { | |||
| open: boolean; | |||
| handleClose: () => void; | |||
| leaveTypes: LeaveType[]; | |||
| @@ -60,6 +61,13 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||
| 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 = ( | |||
| <Box sx={{ display: "flex", flexDirection: { xs: "column", sm: "row" } }}> | |||
| <Box> | |||
| @@ -80,10 +88,17 @@ const PastEntryCalendarModal: React.FC<Props> = ({ | |||
| {t("Has both timesheet and leave entry")} | |||
| </Typography> | |||
| </Box> | |||
| <Box display="flex" alignItems="center" gap={1}> | |||
| <Indicator sx={{ backgroundColor: "error.light" }} /> | |||
| <Typography variant="caption"> | |||
| {t("Public Holiday")} | |||
| </Typography> | |||
| </Box> | |||
| </Stack> | |||
| <PastEntryCalendar | |||
| timesheet={timesheet} | |||
| leaves={leaves} | |||
| publicHolidays={publicHolidays} | |||
| onDateSelect={setSelectedDate} | |||
| onMonthChange={setMonthChange} | |||
| /> | |||
| @@ -307,7 +307,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {"(k) " + t("Projected Cost Performance Index") + " (CPI)"} | |||
| </div> | |||
| {Number(ProjectedCPI) < 1 && ( | |||
| {Number(ProjectedCPI) < 1.2 && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium mx-5 mb-2" | |||
| @@ -320,7 +320,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| </div> | |||
| </> | |||
| )} | |||
| {Number(ProjectedCPI) >= 1 && ( | |||
| {Number(ProjectedCPI) >= 1.2 && ( | |||
| <> | |||
| <div | |||
| 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 { SumOfByClient, SumOfByTeam, sumUpByClient, sumUpByTeam } from './gptFn'; | |||
| import FinancialStatusByProject from './FinnancialStatusByProject'; | |||
| import { generateYearRanges } from './Util'; | |||
| interface Props { | |||
| _teamId: number, | |||
| @@ -17,6 +18,7 @@ interface Props { | |||
| type InputDate = { | |||
| startDate: string; | |||
| endDate: string; | |||
| label?: string; | |||
| } | |||
| type DateParams = { | |||
| @@ -116,16 +118,28 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||
| // 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]); | |||
| const futureList: string[] = useMemo(() => { | |||
| @@ -165,21 +179,22 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||
| }, []); | |||
| const handleFilter = useCallback(async (value: number) => { | |||
| if (isFuture){ | |||
| // if (isFuture){ | |||
| setPeriod(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(_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) | |||
| @@ -212,21 +227,21 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||
| 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> | |||
| </FormControl> | |||
| <FormControlLabel | |||
| {/* <FormControlLabel | |||
| control={ | |||
| <Switch | |||
| checked={!isFuture} | |||
| @@ -236,7 +251,7 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||
| /> | |||
| } | |||
| label={isFuture ? t("Past Years") : t("Future Years")} | |||
| /> | |||
| /> */} | |||
| </Box> | |||
| </CardContent> | |||
| </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; | |||
| } | |||