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