| @@ -78,6 +78,24 @@ export interface FinancialSummaryByClient { | |||||
| numberOfRecords: number; | numberOfRecords: number; | ||||
| } | } | ||||
| export type FinancialByProject = { | |||||
| id: number, | |||||
| projectName: string, | |||||
| projectCode: string, | |||||
| team: string, | |||||
| teamId: number, | |||||
| custId: number, | |||||
| customerName: string, | |||||
| customerCode: string, | |||||
| subsidiary: string, | |||||
| totalFee: number, | |||||
| totalBudget: number, | |||||
| manhourExpense: number, | |||||
| invoicedAmount: number, | |||||
| paidAmount: number, | |||||
| projectExpense: number, | |||||
| } | |||||
| export const preloadFinancialSummaryCard = () => { | export const preloadFinancialSummaryCard = () => { | ||||
| fetchFinancialSummaryCard(); | fetchFinancialSummaryCard(); | ||||
| }; | }; | ||||
| @@ -102,3 +120,13 @@ export const fetchFinancialSummaryByProject = cache(async (endDate: string, team | |||||
| next: { tags: ["financialSummaryByProject"] }, | next: { tags: ["financialSummaryByProject"] }, | ||||
| }); | }); | ||||
| }) | }) | ||||
| export const fetchFinancialSummaryByProjectV2 = cache(async (teamId: number, endDate: string, startDate: string) => { | |||||
| var endpoint = `${BASE_API_URL}/dashboard/getFinancialSummary-final?` | |||||
| if (startDate.length > 0) endpoint += `&startDate=${startDate}` | |||||
| if (endDate.length > 0) endpoint += `&endDate=${endDate}` | |||||
| if (teamId > 0 ) endpoint += `&teamId=${teamId}` | |||||
| return serverFetchJson<FinancialByProject[]>(endpoint, { | |||||
| next: { tags: ["financialSummaryByProject"] }, | |||||
| }); | |||||
| }) | |||||
| @@ -238,7 +238,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| style={dataNegativeStyle} | style={dataNegativeStyle} | ||||
| > | > | ||||
| {CostPerformanceIndex} | |||||
| {CostPerformanceIndex.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 3 })} | |||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | <div style={{ overflow: 'hidden' }}> | ||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | ||||
| @@ -254,7 +254,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| style={dataPositiveStyle} | style={dataPositiveStyle} | ||||
| > | > | ||||
| {CostPerformanceIndex} | |||||
| {CostPerformanceIndex.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 3 })} | |||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | <div style={{ overflow: 'hidden' }}> | ||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | ||||
| @@ -313,7 +313,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| style={dataNegativeStyle} | style={dataNegativeStyle} | ||||
| > | > | ||||
| {ProjectedCPI} | |||||
| {ProjectedCPI.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 3 })} | |||||
| </div> | </div> | ||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | ||||
| <div className="ml-2 mr-2 ">{"(k) = (b) / (d)"}</div> | <div className="ml-2 mr-2 ">{"(k) = (b) / (d)"}</div> | ||||
| @@ -326,7 +326,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| style={dataPositiveStyle} | style={dataPositiveStyle} | ||||
| > | > | ||||
| {ProjectedCPI} | |||||
| {ProjectedCPI.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 3 })} | |||||
| </div> | </div> | ||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | ||||
| <div className="ml-2 mr-2 ">{"(k) = (b) / (d)"}</div> | <div className="ml-2 mr-2 ">{"(k) = (b) / (d)"}</div> | ||||
| @@ -1,5 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import { fetchFinancialSummary, fetchFinancialSummaryByProject, FinancialSummaryByProject, FinancialSummaryType, FinancialSummaryByClient } from '@/app/api/financialsummary'; | |||||
| import { fetchFinancialSummary, fetchFinancialSummaryByProject, FinancialSummaryByProject, FinancialSummaryType, FinancialSummaryByClient, FinancialByProject, fetchFinancialSummaryByProjectV2 } from '@/app/api/financialsummary'; | |||||
| import { Box, Card, CardContent, CardHeader, FormControl, InputLabel, Link, MenuItem, Select, Stack } from '@mui/material'; | import { Box, Card, CardContent, CardHeader, FormControl, InputLabel, Link, MenuItem, Select, Stack } from '@mui/material'; | ||||
| import { usePathname, useSearchParams } from 'next/navigation'; | import { usePathname, useSearchParams } from 'next/navigation'; | ||||
| import { useRouter } from 'next/navigation'; | import { useRouter } from 'next/navigation'; | ||||
| @@ -8,78 +8,83 @@ import { useTranslation } from "react-i18next"; | |||||
| import ProjectFinancialCard from '../ProjectFinancialSummary/ProjectFinancialCard'; | import ProjectFinancialCard from '../ProjectFinancialSummary/ProjectFinancialCard'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | ||||
| import { revalidateTag } from 'next/cache'; | |||||
| import { revalidate } from '@/app/api/financialsummary/actions'; | |||||
| import CircularProgress from '@mui/material/CircularProgress'; | |||||
| import { SumOfByClient, SumOfByTeam, sumUpByClient, sumUpByTeam } from './gptFn'; | |||||
| import FinancialStatusByProject from './FinnancialStatusByProject'; | import FinancialStatusByProject from './FinnancialStatusByProject'; | ||||
| import { summarizeFinancialData } from './gptFn'; | |||||
| interface Props { | interface Props { | ||||
| _financialSumm: FinancialSummaryType[], | |||||
| _teamId: number | null | |||||
| // _financialSumm: FinancialSummaryType[], | |||||
| _teamId: number, | |||||
| financialSummByProject: FinancialByProject[] | |||||
| } | } | ||||
| type InputDate = { | type InputDate = { | ||||
| startDate: string | null; | |||||
| startDate: string; | |||||
| endDate: string; | endDate: string; | ||||
| } | } | ||||
| type DateParams = { | type DateParams = { | ||||
| 1: InputDate; | |||||
| 2: InputDate; | 2: InputDate; | ||||
| 3: InputDate; | 3: InputDate; | ||||
| 4: InputDate; | 4: InputDate; | ||||
| 5: InputDate; | |||||
| } | } | ||||
| const FinancialSummaryPage: React.FC<Props> = ({ | const FinancialSummaryPage: React.FC<Props> = ({ | ||||
| _financialSumm, | |||||
| _teamId | |||||
| _teamId, | |||||
| financialSummByProject | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const searchParams = useSearchParams(); | |||||
| const [financialSumm, setFinancialSumm] = useState(_financialSumm) | |||||
| const [teamId, setTeamId] = useState(_teamId) | |||||
| const [isCardClickedIndex, setIsCardClickedIndex] = useState(_financialSumm[0].team.id); | |||||
| const curr = useMemo(() => dayjs().format(INPUT_DATE_FORMAT), []) | const curr = useMemo(() => dayjs().format(INPUT_DATE_FORMAT), []) | ||||
| const currYear = useMemo(() => dayjs().get("year"), []) | const currYear = useMemo(() => dayjs().get("year"), []) | ||||
| const startDate = useMemo(() => "10-01", []) | const startDate = useMemo(() => "10-01", []) | ||||
| const endDate = useMemo(() => "09-30", []) | const endDate = useMemo(() => "09-30", []) | ||||
| const currFinancialYear = useMemo(() => curr > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) | const currFinancialYear = useMemo(() => curr > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) | ||||
| const [mainData, setMainData] = useState<FinancialByProject[]>(financialSummByProject) | |||||
| const [byTeam, setByTeam] = useState<SumOfByTeam[]>(() => sumUpByTeam(mainData)) // do fetch to set | |||||
| const [byProject, setByProject] = useState<FinancialByProject[]>(financialSummByProject) | |||||
| const [byClient, setByClient] = useState<SumOfByClient[]>(() => sumUpByClient(mainData)) | |||||
| const allTeam = useMemo(()=> { | |||||
| var _allTeam: SumOfByTeam = { | |||||
| id: 0, | |||||
| team: "All Team", | |||||
| totalFee: 0, | |||||
| totalBudget: 0, | |||||
| manhourExpense: 0, | |||||
| projectExpense: 0, | |||||
| invoicedAmount: 0, | |||||
| paidAmount: 0, | |||||
| activeProject: 0, | |||||
| } | |||||
| for (let i = 0; i < byTeam.length; i++) { | |||||
| var curr = byTeam[i] | |||||
| _allTeam["totalFee"] += curr.totalFee | |||||
| _allTeam["totalBudget"] += curr.totalBudget | |||||
| _allTeam["manhourExpense"] += curr.manhourExpense | |||||
| _allTeam["projectExpense"] += curr.projectExpense | |||||
| _allTeam["invoicedAmount"] += curr.invoicedAmount | |||||
| _allTeam["paidAmount"] += curr.paidAmount | |||||
| _allTeam["activeProject"] += curr.activeProject | |||||
| } | |||||
| return _allTeam | |||||
| }, [mainData]) | |||||
| const [teamId, setTeamId] = useState(_teamId) | |||||
| const [isCardClickedIndex, setIsCardClickedIndex] = useState(_teamId || 0); | |||||
| const [period, setPeriod] = useState(0); | const [period, setPeriod] = useState(0); | ||||
| const [isLoading, setIsLoading] = useState(true) | |||||
| const [table1Data, setTable1Data] = useState<FinancialSummaryByProject[]>([]) | |||||
| const [table2Data, setTable2Data] = useState<FinancialSummaryByClient[]>([]) | |||||
| const dateMap: DateParams = useMemo(() => ({ | const dateMap: DateParams = useMemo(() => ({ | ||||
| 1: {startDate: `${currFinancialYear-2}-${startDate}`, endDate: `${currFinancialYear-1}-${endDate}`}, | |||||
| 2: {startDate: `${currFinancialYear-3}-${startDate}`, endDate: `${currFinancialYear-2}-${endDate}`}, | |||||
| 3: {startDate: `${currFinancialYear-4}-${startDate}`, endDate: `${currFinancialYear-3}-${endDate}`}, | |||||
| 4: {startDate: null, endDate: `${currFinancialYear-4}-${endDate}`}, | |||||
| 2: {startDate: `${currFinancialYear-2}-${startDate}`, endDate: `${currFinancialYear-1}-${endDate}`}, | |||||
| 3: {startDate: `${currFinancialYear-3}-${startDate}`, endDate: `${currFinancialYear-2}-${endDate}`}, | |||||
| 4: {startDate: `${currFinancialYear-4}-${startDate}`, endDate: `${currFinancialYear-3}-${endDate}`}, | |||||
| 5: {startDate: "", endDate: `${currFinancialYear-4}-${endDate}`}, | |||||
| }), [currYear, startDate, endDate]) | }), [currYear, startDate, endDate]) | ||||
| const [filter, setFilter] = useState<InputDate>(() => { | |||||
| if (curr <= `${currYear}-${endDate}`) { | |||||
| return ({ | |||||
| startDate: `${currYear - 1}-${startDate}`, | |||||
| endDate: `${currYear}-${endDate}` | |||||
| }) | |||||
| } else { | |||||
| return ({ | |||||
| startDate: `${currYear}-${startDate}`, | |||||
| endDate: `${currFinancialYear}-${endDate}` | |||||
| }) | |||||
| } | |||||
| }) | |||||
| const fetchHtmlTable = useCallback(async (teamId: number | null, endDate: string, startDate: string | null) => { | |||||
| const tableData = await fetchFinancialSummary(endDate , teamId, startDate) | |||||
| setFinancialSumm(tableData) | |||||
| }, [fetchFinancialSummary]) | |||||
| const fetchTable1Data = useCallback(async (teamId: number, endDate: string, startDate?: string) => { | |||||
| const tableData = await fetchFinancialSummaryByProject(endDate , teamId.toString(), startDate) | |||||
| setTable1Data(tableData) | |||||
| const table2Data = summarizeFinancialData(table1Data) | |||||
| console.log(table2Data) | |||||
| setTable2Data(table2Data) | |||||
| }, [fetchFinancialSummaryByProject]) | |||||
| const fetchFinancialSummaryByProject = useCallback(async (endDate: string, startDate: string) => { | |||||
| const data = await fetchFinancialSummaryByProjectV2(_teamId, endDate, startDate) | |||||
| setMainData(data) | |||||
| setByTeam(sumUpByTeam(data)) | |||||
| setByProject(data) | |||||
| setByClient(sumUpByClient(data)) | |||||
| }, [setMainData, setByTeam, setByProject, setByClient]) | |||||
| const handleCardClick = useCallback((teamId: number) => { | const handleCardClick = useCallback((teamId: number) => { | ||||
| setIsCardClickedIndex(teamId) | setIsCardClickedIndex(teamId) | ||||
| @@ -89,9 +94,12 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| const handleFilter = useCallback((value: number) => { | const handleFilter = useCallback((value: number) => { | ||||
| setPeriod(value) | setPeriod(value) | ||||
| console.log(value) | console.log(value) | ||||
| var _startDate: string | null = "" | |||||
| var _startDate: string = "" | |||||
| var _endDate = "" | var _endDate = "" | ||||
| if (value == 0) { | if (value == 0) { | ||||
| _startDate = "" | |||||
| _endDate == "" | |||||
| } else if (value == 1) { | |||||
| if (curr <= `${currYear}-${endDate}`) { | if (curr <= `${currYear}-${endDate}`) { | ||||
| _startDate = `${currYear - 1}-${startDate}` | _startDate = `${currYear - 1}-${startDate}` | ||||
| _endDate = `${currYear}-${endDate}` | _endDate = `${currYear}-${endDate}` | ||||
| @@ -103,27 +111,23 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| _startDate = dateMap[value as keyof DateParams].startDate | _startDate = dateMap[value as keyof DateParams].startDate | ||||
| _endDate = dateMap[value as keyof DateParams].endDate | _endDate = dateMap[value as keyof DateParams].endDate | ||||
| } | } | ||||
| setFilter({startDate: _startDate, endDate: _endDate}) | |||||
| console.log(_startDate) | |||||
| console.log(_endDate) | |||||
| fetchFinancialSummaryByProject(_endDate, _startDate) | |||||
| }, [isCardClickedIndex]) | }, [isCardClickedIndex]) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (financialSumm.length > 0) setIsLoading(false) | |||||
| }, [financialSumm]) | |||||
| useEffect(() => { | |||||
| console.log(teamId) | |||||
| console.log(filter) | |||||
| fetchHtmlTable(teamId, filter.endDate, filter.startDate) | |||||
| if (teamId) { | |||||
| const testing = fetchTable1Data(isCardClickedIndex, filter.endDate, filter.startDate ? filter.startDate : undefined) | |||||
| console.log(testing) | |||||
| if (teamId > 0) { | |||||
| var filterByTeam = mainData.filter(item => item.teamId == teamId) | |||||
| setByProject(filterByTeam) | |||||
| setByClient(sumUpByClient(filterByTeam)) | |||||
| } else { | |||||
| setByProject(financialSummByProject) | |||||
| setByClient(sumUpByClient(mainData)) | |||||
| } | } | ||||
| }, [teamId, filter]) | |||||
| }, [teamId]) | |||||
| useEffect(() => { | |||||
| console.log(searchParams.toString()) | |||||
| }, [searchParams]) | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
| @@ -139,13 +143,15 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| label="Age" | label="Age" | ||||
| onChange={(e) => handleFilter(Number(e.target.value))} | onChange={(e) => handleFilter(Number(e.target.value))} | ||||
| > | > | ||||
| {Array.from({ length: 5 }).map((_, i) => { | |||||
| {Array.from({ length: 6 }).map((_, i) => { | |||||
| if (i == 0) { | if (i == 0) { | ||||
| return <MenuItem key={i} value={i}>{`${currFinancialYear - i - 1} - ${currFinancialYear - i} (current)`}</MenuItem> | |||||
| } else if (i == 4) { | |||||
| return <MenuItem value={i}>{`< ${currYear - i}`}</MenuItem> | |||||
| return <MenuItem key={i} value={i}>{`All`}</MenuItem> | |||||
| } else if (i == 1) { | |||||
| return <MenuItem key={i} value={i}>{`${currFinancialYear - i} - ${currFinancialYear - i + 1} (current year)`}</MenuItem> | |||||
| } else if (i == 5) { | |||||
| return <MenuItem value={i}>{`< ${currYear - i + 1}`}</MenuItem> | |||||
| } else { | } else { | ||||
| return <MenuItem key={i} value={i}>{`${currFinancialYear - i - 1} - ${currFinancialYear - i}`}</MenuItem> | |||||
| return <MenuItem key={i} value={i}>{`${currFinancialYear - i} - ${currFinancialYear - i + 1}`}</MenuItem> | |||||
| } | } | ||||
| } | } | ||||
| )} | )} | ||||
| @@ -154,44 +160,58 @@ const FinancialSummaryPage: React.FC<Props> = ({ | |||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| { !isLoading ? ( | |||||
| <> | |||||
| <Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
| <CardHeader className="text-slate-500" title= {t("Active Project Financial Status")}/> | <CardHeader className="text-slate-500" title= {t("Active Project Financial Status")}/> | ||||
| <CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
| <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | ||||
| {financialSumm.length > 0 && financialSumm.map((record:any) => ( | |||||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={record.team.id} onClick={() => handleCardClick(record.team.id)}> | |||||
| {allTeam && | |||||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={0} onClick={() => handleCardClick(0)}> | |||||
| <ProjectFinancialCard | |||||
| Title={t("All Team")} | |||||
| TeamId={0} | |||||
| TotalActiveProjectNumber={allTeam.activeProject} | |||||
| TotalFees={allTeam.totalFee} | |||||
| TotalBudget={allTeam.totalBudget} | |||||
| TotalCumulative={allTeam.projectExpense + allTeam.invoicedAmount} | |||||
| TotalProjectExpense={allTeam.projectExpense} | |||||
| TotalInvoicedAmount={allTeam.invoicedAmount} | |||||
| TotalUnInvoicedAmount={allTeam.totalFee - allTeam.invoicedAmount} | |||||
| TotalReceivedAmount={allTeam.paidAmount} | |||||
| CashFlowStatus={allTeam.invoicedAmount >= (allTeam.projectExpense + allTeam.invoicedAmount) ? "Positive" : "Negative"} | |||||
| CostPerformanceIndex={allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.invoicedAmount) || 0} | |||||
| ProjectedCashFlowStatus={allTeam.totalFee >= (allTeam.projectExpense + allTeam.invoicedAmount) ? "Positive" : "Negative"} | |||||
| ProjectedCPI={allTeam.totalFee/(allTeam.projectExpense + allTeam.invoicedAmount)} | |||||
| ClickedIndex={isCardClickedIndex} | |||||
| Index={0}/> | |||||
| </div>} | |||||
| {byTeam.length > 0 && byTeam.map((record) => ( | |||||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={record.id} onClick={() => handleCardClick(record.id)}> | |||||
| <ProjectFinancialCard | <ProjectFinancialCard | ||||
| Title={record.teamName == "All Team" ? t("All Team") : record.team.name} | |||||
| TeamId={record.team.id} | |||||
| Title={record.team} | |||||
| TeamId={record.id} | |||||
| TotalActiveProjectNumber={record.activeProject} | TotalActiveProjectNumber={record.activeProject} | ||||
| TotalFees={record.totalFees} | |||||
| TotalFees={record.totalFee} | |||||
| TotalBudget={record.totalBudget} | TotalBudget={record.totalBudget} | ||||
| TotalCumulative={record.cumulativeExpenditure} | |||||
| TotalCumulative={record.projectExpense + record.invoicedAmount} | |||||
| TotalProjectExpense={record.projectExpense} | TotalProjectExpense={record.projectExpense} | ||||
| TotalInvoicedAmount={record.invoicedAmount} | TotalInvoicedAmount={record.invoicedAmount} | ||||
| TotalUnInvoicedAmount={record.nonInvoicedAmount} | |||||
| TotalReceivedAmount={record.receivedAmount} | |||||
| CashFlowStatus={record.cashFlowStatus} | |||||
| CostPerformanceIndex={record.costPerformanceIndex} | |||||
| ProjectedCashFlowStatus={record.projectedCashFlowStatus} | |||||
| ProjectedCPI={record.projectedCostPerformanceIndex} | |||||
| TotalUnInvoicedAmount={record.totalFee - record.invoicedAmount} | |||||
| TotalReceivedAmount={record.paidAmount} | |||||
| CashFlowStatus={record.invoicedAmount >= (record.projectExpense + record.invoicedAmount) ? "Positive" : "Negative"} | |||||
| CostPerformanceIndex={record.invoicedAmount/(record.projectExpense + record.invoicedAmount) || 0} | |||||
| ProjectedCashFlowStatus={record.totalFee >= (record.projectExpense + record.invoicedAmount) ? "Positive" : "Negative"} | |||||
| ProjectedCPI={record.totalFee/(record.projectExpense + record.invoicedAmount)} | |||||
| ClickedIndex={isCardClickedIndex} | ClickedIndex={isCardClickedIndex} | ||||
| Index={record.team.id}/> | |||||
| Index={record.id}/> | |||||
| </div> | </div> | ||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| </CardContent> | </CardContent> | ||||
| <FinancialStatusByProject | |||||
| financialSummByProject={byProject} | |||||
| financialSummByClient={byClient} | |||||
| /> | |||||
| </Card> | </Card> | ||||
| <FinancialStatusByProject | |||||
| financialSummByProject={table1Data}/> | |||||
| </>) | |||||
| : | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| } | |||||
| </> | </> | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ import React from "react"; | |||||
| import FinancialSummaryLoading from "./FinancialSummaryLoading"; | import FinancialSummaryLoading from "./FinancialSummaryLoading"; | ||||
| import FinancialSummary from "./FinancialSummary"; | import FinancialSummary from "./FinancialSummary"; | ||||
| import { fetchUserStaff, searchParamsProps } from "@/app/utils/fetchUtil"; | import { fetchUserStaff, searchParamsProps } from "@/app/utils/fetchUtil"; | ||||
| import { fetchFinancialSummary, fetchFinancialSummaryByProject, FinancialSummaryByProject, FinancialSummaryType } from "@/app/api/financialsummary"; | |||||
| import { fetchFinancialSummary, fetchFinancialSummaryByProject, fetchFinancialSummaryByProjectV2, FinancialSummaryByProject, FinancialSummaryType } from "@/app/api/financialsummary"; | |||||
| import { Grid } from "@mui/material"; | import { Grid } from "@mui/material"; | ||||
| import FinancialStatusByProject from "./FinnancialStatusByProject"; | import FinancialStatusByProject from "./FinnancialStatusByProject"; | ||||
| @@ -16,48 +16,33 @@ interface SubComponents { | |||||
| const FinancialSummaryWrapper: React.FC<searchParamsProps> & SubComponents = async ({ | const FinancialSummaryWrapper: React.FC<searchParamsProps> & SubComponents = async ({ | ||||
| searchParams, | searchParams, | ||||
| }) => { | }) => { | ||||
| const curr = new Date() | |||||
| const currYear = curr.getFullYear() | |||||
| const start = "10-01" | |||||
| const end = "09-30" | |||||
| var defaultEnd: string | |||||
| var defaultStart: string | |||||
| if (curr.toISOString().split('T')[0] <= `${currYear}-${end}`) { | |||||
| defaultStart = `${currYear-1}-${start}` | |||||
| defaultEnd = `${currYear}-${end}` | |||||
| } else { | |||||
| defaultStart = `${currYear}-${start}` | |||||
| defaultEnd = `${currYear+1}-${end}` | |||||
| } | |||||
| // const startDate = searchParams.startDate ?? defaultStart; | |||||
| // const endDate = searchParams.endDate ?? defaultEnd; | |||||
| const userStaff = await fetchUserStaff(); | |||||
| const teamId = userStaff?.isTeamLead ? userStaff.teamId : null; | |||||
| // let financialSumm: FinancialSummaryType[] = []; | |||||
| // let financialSummByProject: FinancialSummaryByProject[] = []; | |||||
| // if (startDate && endDate) { | |||||
| // financialSumm = await fetchFinancialSummary(endDate as string, teamId, startDate as string); | |||||
| // const curr = new Date() | |||||
| // const currYear = curr.getFullYear() | |||||
| // const start = "10-01" | |||||
| // const end = "09-30" | |||||
| // var defaultEnd: string | |||||
| // var defaultStart: string | |||||
| // if (curr.toISOString().split('T')[0] <= `${currYear}-${end}`) { | |||||
| // defaultStart = `${currYear-1}-${start}` | |||||
| // defaultEnd = `${currYear}-${end}` | |||||
| // } else { | |||||
| // defaultStart = `${currYear}-${start}` | |||||
| // defaultEnd = `${currYear+1}-${end}` | |||||
| // } | // } | ||||
| const userStaff = await fetchUserStaff(); | |||||
| const teamId = userStaff?.isTeamLead ? userStaff.teamId : 0; | |||||
| const [ | const [ | ||||
| financialSumm | |||||
| // financialSumm, | |||||
| financialSummByProject | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchFinancialSummary(defaultEnd, teamId, defaultStart) | |||||
| // fetchFinancialSummary(defaultEnd, teamId, defaultStart), | |||||
| fetchFinancialSummaryByProjectV2(teamId, "", "") | |||||
| ]); | ]); | ||||
| // if (teamId) { | |||||
| // financialSummByProject = await fetchFinancialSummaryByProject(endDate as string, teamId.toString(), startDate as string) | |||||
| // } else if (paramTeamId) { | |||||
| // console.log(paramTeamId) | |||||
| // console.log(startDate) | |||||
| // console.log(endDate) | |||||
| // financialSummByProject = await fetchFinancialSummaryByProject(endDate as string, paramTeamId as string, startDate as string) | |||||
| // } | |||||
| // console.log(financialSumm) | |||||
| // console.log(financialSummByProject) | // console.log(financialSummByProject) | ||||
| return ( | return ( | ||||
| <Grid> | <Grid> | ||||
| <FinancialSummary _financialSumm={financialSumm} _teamId={teamId}/> | |||||
| {/* <FinancialStatusByProject financialSummByProject={financialSummByProject}/> */} | |||||
| <FinancialSummary _teamId={teamId} financialSummByProject={financialSummByProject}/> | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -3,6 +3,7 @@ | |||||
| import { | import { | ||||
| FinancialSummaryByProject, | FinancialSummaryByProject, | ||||
| FinancialSummaryByClient, | FinancialSummaryByClient, | ||||
| FinancialByProject, | |||||
| } from "@/app/api/financialsummary"; | } from "@/app/api/financialsummary"; | ||||
| import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
| import { useEffect, useMemo, useState } from "react"; | import { useEffect, useMemo, useState } from "react"; | ||||
| @@ -10,31 +11,29 @@ import CustomDatagrid from "../CustomDatagrid"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import { Box, Grid } from "@mui/material"; | import { Box, Grid } from "@mui/material"; | ||||
| import { summarizeFinancialData } from "./gptFn"; | |||||
| import { SumOfByClient } from "./gptFn"; | |||||
| // import { summarizeFinancialData } from "./gptFn"; | |||||
| interface Props { | interface Props { | ||||
| financialSummByProject: FinancialSummaryByProject[]; | |||||
| // financialSummByClient: FinancialSummaryByClient[]; | |||||
| financialSummByProject: FinancialByProject[]; | |||||
| financialSummByClient: SumOfByClient[]; | |||||
| } | } | ||||
| type SearchQuery = Partial<Omit<FinancialSummaryByProject, "id">>; | type SearchQuery = Partial<Omit<FinancialSummaryByProject, "id">>; | ||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| type SearchQuery2 = Partial<Omit<FinancialSummaryByClient, "id">>; | |||||
| type SearchQuery2 = Partial<Omit<SumOfByClient, "id">>; | |||||
| type SearchParamNames2 = keyof SearchQuery2; | type SearchParamNames2 = keyof SearchQuery2; | ||||
| const FinancialStatusByProject: React.FC<Props> = ({ | const FinancialStatusByProject: React.FC<Props> = ({ | ||||
| financialSummByProject, | financialSummByProject, | ||||
| // financialSummByClient, | |||||
| financialSummByClient | |||||
| }) => { | }) => { | ||||
| console.log(financialSummByProject); | console.log(financialSummByProject); | ||||
| // console.log(financialSummByClient); | // console.log(financialSummByClient); | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [filteredByProjectRows, setFilteredByProjectRows] = useState(financialSummByProject); | const [filteredByProjectRows, setFilteredByProjectRows] = useState(financialSummByProject); | ||||
| const [filteredByClientRows, setFilteredByClientRows] = useState(() => { | |||||
| console.log(summarizeFinancialData(financialSummByProject)) | |||||
| return summarizeFinancialData(financialSummByProject) ?? [] | |||||
| }); | |||||
| const [filteredByClientRows, setFilteredByClientRows] = useState(financialSummByClient); | |||||
| console.log(filteredByProjectRows); | console.log(filteredByProjectRows); | ||||
| console.log(filteredByClientRows); | console.log(filteredByClientRows); | ||||
| @@ -59,8 +58,8 @@ console.log(filteredByProjectRows); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setFilteredByProjectRows(financialSummByProject); | setFilteredByProjectRows(financialSummByProject); | ||||
| setFilteredByClientRows(summarizeFinancialData(financialSummByProject)) | |||||
| }, [financialSummByProject]); | |||||
| setFilteredByClientRows(financialSummByClient) | |||||
| }, [financialSummByProject, financialSummByClient]); | |||||
| const columns1 = [ | const columns1 = [ | ||||
| { | { | ||||
| @@ -118,9 +117,10 @@ console.log(filteredByProjectRows); | |||||
| headerName: "CPI", | headerName: "CPI", | ||||
| minWidth: 50, | minWidth: 50, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var cpi = params.invoicedAmount/(params.projectExpense + params.invoicedAmount) || 0 | |||||
| return ( | return ( | ||||
| <span className={params.row.cpi >= 1 ? greenColor : redColor}> | |||||
| {params.row.cpi.toLocaleString(undefined, { | |||||
| <span className={cpi >= 1 ? greenColor : redColor}> | |||||
| {cpi.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -134,7 +134,8 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("Projected Cash Flow Status"), | headerName: t("Projected Cash Flow Status"), | ||||
| minWidth: 100, | minWidth: 100, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| if (params.row.totalFee >= params.row.cumulativeExpenditure) { | |||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| if (params.row.totalFee >= cumulativeExpenditure) { | |||||
| return <span className={greenColor}>{t("Positive")}</span>; | return <span className={greenColor}>{t("Positive")}</span>; | ||||
| } else { | } else { | ||||
| return <span className={redColor}>{t("Negative")}</span>; | return <span className={redColor}>{t("Negative")}</span>; | ||||
| @@ -147,11 +148,12 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("Projected CPI"), | headerName: t("Projected CPI"), | ||||
| minWidth: 50, | minWidth: 50, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var projectedCpi = params.row.totalFee/(params.row.projectExpense + params.row.invoicedAmount) | |||||
| return ( | return ( | ||||
| <span | <span | ||||
| className={params.row.projectedCpi >= 1 ? greenColor : redColor} | |||||
| className={projectedCpi >= 1 ? greenColor : redColor} | |||||
| > | > | ||||
| {params.row.projectedCpi.toLocaleString(undefined, { | |||||
| {projectedCpi.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -202,10 +204,11 @@ console.log(filteredByProjectRows); | |||||
| minWidth: 250, | minWidth: 250, | ||||
| type: "number", | type: "number", | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.cumulativeExpenditure.toLocaleString(undefined, { | |||||
| {cumulativeExpenditure.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -274,10 +277,11 @@ console.log(filteredByProjectRows); | |||||
| minWidth: 250, | minWidth: 250, | ||||
| type: "number", | type: "number", | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var nonInvoiced = params.row.totalFee - params.row.invoicedAmount | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.nonInvoicedAmount.toLocaleString(undefined, { | |||||
| {nonInvoiced.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -286,8 +290,8 @@ console.log(filteredByProjectRows); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "receivedAmount", | |||||
| field: "receivedAmount", | |||||
| id: "paidAmount", | |||||
| field: "paidAmount", | |||||
| headerName: t("Total Received Amount") + t("HKD"), | headerName: t("Total Received Amount") + t("HKD"), | ||||
| minWidth: 250, | minWidth: 250, | ||||
| type: "number", | type: "number", | ||||
| @@ -295,7 +299,7 @@ console.log(filteredByProjectRows); | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.receivedAmount.toLocaleString(undefined, { | |||||
| {params.row.paidAmount.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -331,8 +335,8 @@ console.log(filteredByProjectRows); | |||||
| minWidth: 80, | minWidth: 80, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "numberOfRecords", | |||||
| field: "numberOfRecords", | |||||
| id: "sumOfProjects", | |||||
| field: "sumOfProjects", | |||||
| headerName: t("Total Project Involved"), | headerName: t("Total Project Involved"), | ||||
| minWidth: 80, | minWidth: 80, | ||||
| }, | }, | ||||
| @@ -342,7 +346,8 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("Cash Flow Status"), | headerName: t("Cash Flow Status"), | ||||
| minWidth: 100, | minWidth: 100, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| return params.row.invoicedAmount >= params.row.cumulativeExpenditure ? | |||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| return params.row.invoicedAmount >= cumulativeExpenditure ? | |||||
| <span className={greenColor}>{t("Positive")}</span> | <span className={greenColor}>{t("Positive")}</span> | ||||
| : <span className={redColor}>{t("Negative")}</span> | : <span className={redColor}>{t("Negative")}</span> | ||||
| }, | }, | ||||
| @@ -353,7 +358,8 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("CPI"), | headerName: t("CPI"), | ||||
| minWidth: 50, | minWidth: 50, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var cpi = params.row.cumulativeExpenditure != 0 ? params.row.invoicedAmount/params.row.cumulativeExpenditure : 0 | |||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| var cpi = cumulativeExpenditure != 0 ? params.row.invoicedAmount/cumulativeExpenditure : 0 | |||||
| var cpiString = cpi.toLocaleString(undefined, { | var cpiString = cpi.toLocaleString(undefined, { | ||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| @@ -369,7 +375,8 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("Projected Cash Flow Status"), | headerName: t("Projected Cash Flow Status"), | ||||
| minWidth: 100, | minWidth: 100, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var status = params.row.invoiceAmount >= params.row.cumulativeExpenditure | |||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| var status = params.row.invoiceAmount >= cumulativeExpenditure | |||||
| return status ? | return status ? | ||||
| <span className={greenColor}>{t("Positive")}</span> | <span className={greenColor}>{t("Positive")}</span> | ||||
| : <span className={redColor}>{t("Negative")}</span> | : <span className={redColor}>{t("Negative")}</span> | ||||
| @@ -381,7 +388,8 @@ console.log(filteredByProjectRows); | |||||
| headerName: t("Projected CPI"), | headerName: t("Projected CPI"), | ||||
| minWidth: 50, | minWidth: 50, | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var projectCpi = params.row.cumulativeExpenditure != 0 ? params.row.totalFee/params.row.cumulativeExpenditure : 0 | |||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| var projectCpi = cumulativeExpenditure != 0 ? params.row.totalFee/cumulativeExpenditure : 0 | |||||
| var projectCpiString = projectCpi.toLocaleString(undefined, { | var projectCpiString = projectCpi.toLocaleString(undefined, { | ||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| @@ -436,10 +444,11 @@ console.log(filteredByProjectRows); | |||||
| minWidth: 280, | minWidth: 280, | ||||
| type: "number", | type: "number", | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.cumulativeExpenditure.toLocaleString(undefined, { | |||||
| {cumulativeExpenditure.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -508,10 +517,11 @@ console.log(filteredByProjectRows); | |||||
| minWidth: 250, | minWidth: 250, | ||||
| type: "number", | type: "number", | ||||
| renderCell: (params: any) => { | renderCell: (params: any) => { | ||||
| var uninvoiced = params.row.totalFee - params.row.invoicedAmount | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.nonInvoicedAmount.toLocaleString(undefined, { | |||||
| {uninvoiced.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -520,8 +530,8 @@ console.log(filteredByProjectRows); | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "receivedAmount", | |||||
| field: "receivedAmount", | |||||
| id: "paidAmount", | |||||
| field: "paidAmount", | |||||
| headerName: t("Total Received Amount") + t("HKD"), | headerName: t("Total Received Amount") + t("HKD"), | ||||
| minWidth: 250, | minWidth: 250, | ||||
| type: "number", | type: "number", | ||||
| @@ -529,7 +539,7 @@ console.log(filteredByProjectRows); | |||||
| return ( | return ( | ||||
| <span> | <span> | ||||
| $ | $ | ||||
| {params.row.receivedAmount.toLocaleString(undefined, { | |||||
| {params.row.paidAmount.toLocaleString(undefined, { | |||||
| minimumFractionDigits: 2, | minimumFractionDigits: 2, | ||||
| maximumFractionDigits: 2, | maximumFractionDigits: 2, | ||||
| })} | })} | ||||
| @@ -1,40 +1,86 @@ | |||||
| import { FinancialSummaryByProject, FinancialSummaryByClient } from "@/app/api/financialsummary"; | |||||
| import { FinancialSummaryByProject, FinancialSummaryByClient, FinancialSummaryType, FinancialByProject } from "@/app/api/financialsummary"; | |||||
| export type SumOfByTeam = { | |||||
| id: number, | |||||
| team: string, | |||||
| totalFee: number, | |||||
| totalBudget: number, | |||||
| manhourExpense: number, | |||||
| projectExpense: number, | |||||
| invoicedAmount: number, | |||||
| paidAmount: number, | |||||
| activeProject: number, | |||||
| } | |||||
| export function summarizeFinancialData(data: FinancialSummaryByProject[]): FinancialSummaryByClient[] { | |||||
| const result = data.reduce<Record<number, FinancialSummaryByClient>>((acc, item) => { | |||||
| export type SumOfByClient = { | |||||
| id: number, // custId | |||||
| customerCode: string, | |||||
| customerName: string, | |||||
| totalFee: number, | |||||
| totalBudget: number, | |||||
| manhourExpense: number, | |||||
| projectExpense: number, | |||||
| invoicedAmount: number, | |||||
| paidAmount: number, | |||||
| sumOfProjects: number, | |||||
| } | |||||
| export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] { | |||||
| const result = data.reduce<Record<number, SumOfByClient>>((acc, item) => { | |||||
| if (!acc[item.custId]) { | if (!acc[item.custId]) { | ||||
| acc[item.custId] = { | acc[item.custId] = { | ||||
| id: item.custId, // Set id to custId | |||||
| customerName: item.customerName, // First item's customerName | |||||
| customerCode: item.customerCode, // First item's customerCode | |||||
| subsidiaryName: item.subsidiaryName, // First item's subsidiaryName | |||||
| id: item.custId, | |||||
| customerCode: item.customerName, | |||||
| customerName: item.customerCode, | |||||
| totalFee: 0, | totalFee: 0, | ||||
| totalBudget: 0, | totalBudget: 0, | ||||
| cumulativeExpenditure: 0, | |||||
| manhourExpense: 0, | manhourExpense: 0, | ||||
| projectExpense: 0, | projectExpense: 0, | ||||
| invoicedAmount: 0, | invoicedAmount: 0, | ||||
| nonInvoicedAmount: 0, | |||||
| receivedAmount: 0, | |||||
| numberOfRecords: 0, // Initialize record count | |||||
| paidAmount: 0, | |||||
| sumOfProjects: 0 | |||||
| }; | }; | ||||
| } | } | ||||
| // Sum the numeric fields | // Sum the numeric fields | ||||
| acc[item.custId].totalFee += item.totalFee; | acc[item.custId].totalFee += item.totalFee; | ||||
| acc[item.custId].totalBudget += item.totalBudget; | acc[item.custId].totalBudget += item.totalBudget; | ||||
| acc[item.custId].cumulativeExpenditure += item.cumulativeExpenditure; | |||||
| acc[item.custId].manhourExpense += item.manhourExpense; | acc[item.custId].manhourExpense += item.manhourExpense; | ||||
| acc[item.custId].projectExpense += item.projectExpense; | acc[item.custId].projectExpense += item.projectExpense; | ||||
| acc[item.custId].invoicedAmount += item.invoicedAmount; | acc[item.custId].invoicedAmount += item.invoicedAmount; | ||||
| acc[item.custId].nonInvoicedAmount += item.nonInvoicedAmount; | |||||
| acc[item.custId].receivedAmount += item.receivedAmount; | |||||
| acc[item.custId].numberOfRecords += 1; // Increment record count | |||||
| acc[item.custId].paidAmount += item.paidAmount; | |||||
| acc[item.custId].sumOfProjects += 1; | |||||
| return acc; | |||||
| }, {}); | |||||
| return Object.values(result); | |||||
| } | |||||
| export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] { | |||||
| const result = data.reduce<Record<number, SumOfByTeam>>((acc, item) => { | |||||
| if (!acc[item.teamId]) { | |||||
| acc[item.teamId] = { | |||||
| id: item.teamId, | |||||
| team: item.team, | |||||
| totalFee: 0, | |||||
| totalBudget: 0, | |||||
| manhourExpense: 0, | |||||
| projectExpense: 0, | |||||
| invoicedAmount: 0, | |||||
| paidAmount: 0, | |||||
| activeProject: 0 | |||||
| }; | |||||
| } | |||||
| // Sum the numeric fields | |||||
| acc[item.teamId].totalFee += item.totalFee; | |||||
| acc[item.teamId].totalBudget += item.totalBudget; | |||||
| acc[item.teamId].manhourExpense += item.manhourExpense; | |||||
| acc[item.teamId].projectExpense += item.projectExpense; | |||||
| acc[item.teamId].invoicedAmount += item.invoicedAmount; | |||||
| acc[item.teamId].paidAmount += item.paidAmount; | |||||
| acc[item.teamId].activeProject += 1; | |||||
| return acc; | return acc; | ||||
| }, {}); | }, {}); | ||||
| // Convert the result object to an array | // Convert the result object to an array | ||||
| return Object.values(result); | return Object.values(result); | ||||
| } | |||||
| } | |||||