"use client"; import * as React from "react"; import Grid from "@mui/material/Grid"; import { useState, useEffect, useMemo } from "react"; import Paper from "@mui/material/Paper"; import { TFunction } from "i18next"; import { useTranslation } from "react-i18next"; import { Card, CardHeader } from "@mui/material"; import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; import ReactApexChart from "react-apexcharts"; import { ApexOptions } from "apexcharts"; import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import ReportProblemIcon from "@mui/icons-material/ReportProblem"; import dynamic from "next/dynamic"; import "../../app/global.css"; import { AnyARecord, AnyCnameRecord } from "dns"; import SearchBox, { Criterion } from "../SearchBox"; import ProgressByClientSearch from "@/components/ProgressByClientSearch"; import { Suspense } from "react"; import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; import { fetchProjectsCashFlow,fetchProjectsCashFlowMonthlyChart,fetchProjectsCashFlowReceivableAndExpenditure,fetchProjectsCashFlowAnticipate,fetchProjectsCashFlowLedger} from "@/app/api/cashflow"; import { Input, Label } from "reactstrap"; import { CashFlow } from "@/app/api/cashflow"; import dayjs from 'dayjs'; import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; import Typography from "@mui/material/Typography"; import { useSearchParams } from 'next/navigation'; interface Props { projects: CashFlow[]; } type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const ProjectCashFlow: React.FC = () => { const { t } = useTranslation("dashboard"); const searchParams = useSearchParams(); const projectId = searchParams.get('projectId'); const todayDate = new Date(); const [selectionModel, setSelectionModel]: any[] = useState([]); const [projectData, setProjectData]: any[] = React.useState([]); const [filteredResult, setFilteredResult]:any[] = useState([]); const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]); const [monthlyIncomeList, setMonthlyIncomeList]: any[] = React.useState([]); const [monthlyCumulativeIncomeList, setMonthlyCumulativeIncomeList]: any[] = React.useState([]); const [monthlyExpenditureList, setMonthlyExpenditureList]: any[] = React.useState([]); const [monthlyCumulativeExpenditureList, setMonthlyCumulativeExpenditureList]: any[] = React.useState([]); const [monthlyChartLeftMax, setMonthlyChartLeftMax]: any[] = React.useState(10); const [monthlyChartRightMax, setMonthlyChartRightMax]: any[] = React.useState(10); const [monthlyAnticipateLeftMax, setMonthlyAnticipateLeftMax]: any[] = React.useState(10); const [receivedPercentage,setReceivedPercentage]: any[] = React.useState(0); const [invoicedPercentage,setInvoicedPercentage]: any[] = React.useState(0); const [totalFee,setTotalFee]: any[] = React.useState(0); const [totalBudget,setTotalBudget]: any[] = React.useState(0); const [totalInvoiced,setTotalInvoiced]: any[] = React.useState(0); const [totalReceived,setTotalReceived]: any[] = React.useState(0); const [receivable,setReceivable]: any[] = React.useState(0); const [totalExpenditure,setTotalExpenditure]: any[] = React.useState(0); const [expenditureReceivable,setExpenditureReceivable]: any[] = React.useState(0); const [expenditurePercentage,setExpenditurePercentage]: any[] = React.useState(0); const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); const [ledgerData, setLedgerData]: any[] = React.useState([]); const [isInitializing, setIsInitializing] = useState(true); const [cashFlowYear, setCashFlowYear]: any[] = React.useState( todayDate.getFullYear(), ); const [anticipateCashFlowYear, setAnticipateCashFlowYear]: any[] = React.useState( todayDate.getFullYear(), ); const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { if (!isInitializing) { setSelectionModel(newSelectionModel); console.log(newSelectionModel) console.log(projectData) const selectedRowsData = projectData.filter((row: any) => newSelectionModel.includes(row.id) ); const projectIdList = selectedRowsData.map((row: any) => row.id); console.log(selectedRowsData) setSelectedProjectIdList(projectIdList); } }; const fetchData = async () => { const cashFlowProject = await fetchProjectsCashFlow(); console.log(cashFlowProject) setProjectData(cashFlowProject) setFilteredResult(cashFlowProject) } const fetchChartData = async () => { const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); const monthlyIncome = [] const cumulativeIncome = [] const monthlyExpenditure = [] const cumulativeExpenditure = [] var leftMax = 0 var rightMax = 0 if (cashFlowMonthlyChartData.length !== 0) { for (var i = 0; i < cashFlowMonthlyChartData[0].incomeList.length; i++) { if (leftMax < cashFlowMonthlyChartData[0].incomeList[i].income || leftMax < cashFlowMonthlyChartData[0].expenditureList[i].expenditure){ leftMax = Math.max(cashFlowMonthlyChartData[0].incomeList[i].income,cashFlowMonthlyChartData[0].expenditureList[i].expenditure) } monthlyIncome.push(cashFlowMonthlyChartData[0].incomeList[i].income) cumulativeIncome.push(cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome) } for (var i = 0; i < cashFlowMonthlyChartData[0].expenditureList.length; i++) { if (rightMax < cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome || rightMax < cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure){ rightMax = Math.max(cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome,cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure) } monthlyExpenditure.push(cashFlowMonthlyChartData[0].expenditureList[i].expenditure) cumulativeExpenditure.push(cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure) } setMonthlyIncomeList(monthlyIncome) setMonthlyCumulativeIncomeList(cumulativeIncome) setMonthlyExpenditureList(monthlyExpenditure) setMonthlyCumulativeExpenditureList(cumulativeExpenditure) setMonthlyChartLeftMax(leftMax) setMonthlyChartRightMax(rightMax) } else { setMonthlyIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyCumulativeIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyCumulativeExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) } } const fetchReceivableAndExpenditureData = async () => { console.log("s2") if (selectedProjectIdList.length === 0) { setReceivedPercentage(0) setInvoicedPercentage(0) setTotalFee(0) setTotalInvoiced(0) setTotalReceived(0) setReceivable(0) setExpenditurePercentage(0) setTotalBudget(0) setTotalExpenditure(0) setExpenditureReceivable(0) } else { const cashFlowReceivableAndExpenditureData = await fetchProjectsCashFlowReceivableAndExpenditure(selectedProjectIdList); if(cashFlowReceivableAndExpenditureData.length !== 0){ setReceivedPercentage(cashFlowReceivableAndExpenditureData[0].receivedPercentage) setInvoicedPercentage(cashFlowReceivableAndExpenditureData[0].invoicedPercentage) setTotalFee(cashFlowReceivableAndExpenditureData[0].totalProjectFee) setTotalInvoiced(cashFlowReceivableAndExpenditureData[0].totalInvoiced) setTotalReceived(cashFlowReceivableAndExpenditureData[0].totalReceived) setReceivable(cashFlowReceivableAndExpenditureData[0].receivable) setExpenditurePercentage(cashFlowReceivableAndExpenditureData[0].expenditurePercentage) setTotalBudget(cashFlowReceivableAndExpenditureData[0].totalBudget) setTotalExpenditure(cashFlowReceivableAndExpenditureData[0].totalExpenditure) setExpenditureReceivable(cashFlowReceivableAndExpenditureData[0].expenditureReceivable) } } } const fetchAnticipateData = async () => { const cashFlowAnticipateData = await fetchProjectsCashFlowAnticipate(selectedProjectIdList,anticipateCashFlowYear); const monthlyAnticipateIncome = [] var anticipateLeftMax = 0 if(cashFlowAnticipateData.length !== 0){ for (var i = 0; i < cashFlowAnticipateData[0].anticipateIncomeList.length; i++) { if (anticipateLeftMax < cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome){ anticipateLeftMax = Math.max(cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome,cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome) } monthlyAnticipateIncome.push(cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome) } setMonthlyAnticipateIncomeList(monthlyAnticipateIncome) } else { setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) } if(cashFlowAnticipateData.length !== 0){ if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) { const anticipateExpenditureList = [] for (var i = 0; i < cashFlowAnticipateData[0].anticipateExpenditureList.length; i++) { const subAnticipateExpenditure = [] var duration = cashFlowAnticipateData[0].anticipateExpenditureList[i].Duration var month = cashFlowAnticipateData[0].anticipateExpenditureList[i].startMonth const anticipateExpenditure = cashFlowAnticipateData[0].anticipateExpenditureList[i].aniticipateExpenditure for (var j = 1; j < 13; j++){ if (month === j && duration > 0) { subAnticipateExpenditure.push(anticipateExpenditure) month = month + 1 duration = duration - 1 } else { subAnticipateExpenditure.push(0) } } anticipateExpenditureList.push(subAnticipateExpenditure) } const result = new Array(anticipateExpenditureList[0].length).fill(0); for (const arr of anticipateExpenditureList) { for (let i = 0; i < arr.length; i++) { result[i] += arr[i]; } } setMonthlyAnticipateExpenditureList(result) for (var i = 0; i < monthlyAnticipateIncome.length; i++) { if (anticipateLeftMax < monthlyAnticipateIncome[i] || anticipateLeftMax < result[i]){ anticipateLeftMax = Math.max(monthlyAnticipateIncome[i],result[i]) } setMonthlyAnticipateLeftMax(anticipateLeftMax) } } else { setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) } } } const fetchProjectCashFlowLedger = async () => { const cashFlowLedgerData = await fetchProjectsCashFlowLedger(selectedProjectIdList); setLedgerData(cashFlowLedgerData) } useEffect(() => { if (projectId !== null) { setSelectedProjectIdList([parseInt(projectId)]) setSelectionModel([parseInt(projectId)]); } fetchData().then(() => { setIsInitializing(false); }); }, []); useEffect(() => { fetchChartData(); fetchReceivableAndExpenditureData(); fetchAnticipateData(); fetchProjectCashFlowLedger(); }, [cashFlowYear,selectedProjectIdList]); useEffect(() => { fetchAnticipateData(); }, [anticipateCashFlowYear,selectedProjectIdList]); const columns = [ { id: "projectCode", field: "projectCode", headerName: t("Project Code"), flex: 1, }, { id: "projectName", field: "projectName", headerName: t("Project Name"), flex: 1, }, { id: "team", field: "team", headerName: t("Team"), flex: 1, }, { id: "teamLead", field: "teamLead", headerName: t("Team Leader"), flex: 1, }, { id: "startDate", field: "startDate", headerName: t("Start Date"), flex: 1, }, { id: "targetEndDate", field: "targetEndDate", headerName: t("Target End Date"), flex: 1, }, { id: "client", field: "client", headerName: t("Client"), flex: 1, }, { id: "subsidiary", field: "subsidiary", headerName: t("Subsidiary"), flex: 1, }, ]; const ledgerColumns = [ { id: "date", field: "date", headerName: t("Date"), flex: 0.5, }, { id: "expenditure", field: "expenditure", headerName: t("Expenditure (HKD)"), flex: 0.6, renderCell: (params:any) => { return ( ${params.row.expenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ) } }, { id: "income", field: "income", headerName: t("Income (HKD)"), flex: 0.6, renderCell: (params:any) => { return ( ${params.row.income.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ) } }, { id: "balance", field: "balance", headerName: t("Cash Flow Balance (HKD)"), flex: 0.6, renderCell: (params:any) => { if (params.row.balance < 0) { return ( (${Math.abs(params.row.balance).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}) ) } else { return ( ${params.row.balance.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ) } }, }, { id: "remarks", field: "remarks", headerName: t("Remarks"), flex: 1, }, ]; const options: ApexOptions = { tooltip: { y: { formatter: function(val) { return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } } }, chart: { height: 350, type: "line", }, stroke: { width: [0, 0, 2, 2], }, plotOptions: { bar: { horizontal: false, distributed: false, }, }, dataLabels: { enabled: false, }, xaxis: { categories: [ t("JAN"), t("FEB"), t("MAR"), t("APR"), t("MAY"), t("JUN"), t("JUL"), t("AUG"), t("SEP"), t("OCT"), t("NOV"), t("DEC"), ], }, yaxis: [ { title: { text: t("Monthly Income and Expenditure (HKD)"), style: { fontSize: "13px", } }, min: 0, max: monthlyChartLeftMax, tickAmount: 5, labels: { formatter: function (val) { return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } } }, { show: false, seriesName: "Monthly Expenditure", title: { text: "Monthly Expenditure (HKD)", }, min: 0, max: monthlyChartLeftMax, tickAmount: 5, }, { seriesName: "Cumulative Income", opposite: true, title: { text: t("Cumulative Income and Expenditure (HKD)"), style: { fontSize: "13px", } }, min: 0, max: monthlyChartRightMax, tickAmount: 5, labels: { formatter: function (val) { return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } } }, { show: false, seriesName: "Cumulative Expenditure", opposite: true, title: { text: "Cumulative Expenditure (HKD)", }, min: 0, max: monthlyChartRightMax, tickAmount: 5, }, ], grid: { borderColor: "#f1f1f1", }, annotations: {}, series: [ { name: t("Monthly Income"), type: "column", color: "#ffde91", data: monthlyIncomeList, }, { name: t("Monthly Expenditure"), type: "column", color: "#82b59a", data: monthlyExpenditureList, }, { name: t("Cumulative Income"), type: "line", color: "#EE6D7A", data: monthlyCumulativeIncomeList, }, { name: t("Cumulative Expenditure"), type: "line", color: "#7cd3f2", data: monthlyCumulativeExpenditureList, }, ], }; const anticipateOptions: ApexOptions = { tooltip: { y: { formatter: function(val) { return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } } }, chart: { height: 350, type: "line", }, stroke: { width: [0, 0, 2, 2], }, plotOptions: { bar: { horizontal: false, distributed: false, }, }, dataLabels: { enabled: false, }, xaxis: { categories: [ t("JAN"), t("FEB"), t("MAR"), t("APR"), t("MAY"), t("JUN"), t("JUL"), t("AUG"), t("SEP"), t("OCT"), t("NOV"), t("DEC"), ], }, yaxis: [ { title: { text: t("Anticipate Monthly Income and Expenditure") + t("HKD"), style: { fontSize: "13px", } }, min: 0, max: monthlyAnticipateLeftMax, tickAmount: 5, labels: { formatter: function (val) { return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } } }, ], grid: { borderColor: "#f1f1f1", }, annotations: {}, series: [ { name: t("Monthly Income"), type: "column", color: "#f1c48a", data: monthlyAnticipateIncomeList, }, { name: t("Monthly Expenditure"), type: "column", color: "#89d7f3", data: monthlyAnticipateExpenditureList, } ], }; const accountsReceivableOptions: ApexOptions = { colors: ["#20E647"], series: [receivedPercentage,invoicedPercentage], chart: { height: 50, type: "radialBar", }, plotOptions: { radialBar: { hollow: { size: "70%", background: "#ffffff", }, track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15, }, }, dataLabels: { name: { show: true, fontSize: "0.9em", }, value: { color: "#3e98c7", fontSize: "1.5em", show: true, }, total: { show: true, color: "#20E647", fontSize: "0.9em", label: t('Receivable') + " / " + t('Invoiced'), formatter: function (w:any) { return receivedPercentage + "% / " + invoicedPercentage + "%" }, } }, }, }, fill: { type: "gradient", gradient: { shade: "dark", type: "vertical", gradientToColors: ["#87D4F9"], stops: [0, 100], }, }, stroke: { lineCap: "round", }, labels: ["Received Amount","Invoiced Amount"], }; const expenditureOptions: ApexOptions = { colors: ["#20E647"], series: [expenditurePercentage], chart: { height: 350, type: "radialBar", }, plotOptions: { radialBar: { hollow: { size: "70%", background: "#ffffff", }, track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15, }, }, dataLabels: { name: { show: true, fontSize: "0.9em", }, value: { color: "#3e98c7", fontSize: "1.5em", show: true, }, }, }, }, fill: { type: "gradient", gradient: { shade: "dark", type: "vertical", gradientToColors: ["#87D4F9"], stops: [0, 100], }, }, stroke: { lineCap: "round", }, labels: [t("Expenditure")], }; const rows = [ { id: 1, projectCode: "M1001", projectName: "Consultancy Project A", team: "XXX", teamLeader: "XXX", startDate: "01/07/2022", targetEndDate: "01/04/2024", client: "Client B", subsidiary: "N/A", }, { id: 2, projectCode: "M1301", projectName: "Consultancy Project AAAA", team: "XXX", teamLeader: "XXX", startDate: "01/09/2022", targetEndDate: "20/02/2024", client: "Client C", subsidiary: "Subsidiary A", }, { id: 3, projectCode: "M1354", projectName: "Consultancy Project BBB", team: "YYY", teamLeader: "YYY", startDate: "01/02/2023", targetEndDate: "31/01/2024", client: "Client D", subsidiary: "Subsidiary C", }, ]; const ledgerRows = [ { id: 1, date: "Feb 2023", expenditure: "-", income: "100,000.00", cashFlowBalance: "100,000.00", remarks: "Payment Milestone 1 (10%)", }, { id: 2, date: "Feb 2023", expenditure: "160,000.00", income: "-", cashFlowBalance: "(60,000.00)", remarks: "Monthly Manpower Expenditure", }, { id: 3, date: "Mar 2023", expenditure: "160,000.00", income: "-", cashFlowBalance: "(180,000.00)", remarks: "Monthly Manpower Expenditure", }, { id: 4, date: "Apr 2023", expenditure: "120,000.00", income: "-", cashFlowBalance: "(300,000.00)", remarks: "Monthly Manpower Expenditure", }, { id: 5, date: "May 2023", expenditure: "-", income: "200,000.00", cashFlowBalance: "(100,000.00)", remarks: "Payment Milestone 2 (20%)", }, { id: 6, date: "May 2023", expenditure: "40,000.00", income: "-", cashFlowBalance: "(140,000.00)", remarks: "Monthly Manpower Expenditure", }, ]; const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Project Code"), paramName: "projectCode", type: "text" }, { label: t("Project Name"), paramName: "projectName", type: "text" }, { label: t("Start Date From"), label2: t("Start Date To"), paramName: "startDateFrom", type: "dateRange", }, ], [t], ); function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean { console.log(startDate) console.log(endDate) if (!startDate || !endDate) { return false; } const dateToCheckObj = new Date(dateToCheck); const startDateObj = new Date(startDate); const endDateObj = new Date(endDate); console.log(dateToCheckObj) return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj; } return ( <> {/* }> */} {t('Project Cash Flow')} { console.log(query) setFilteredResult( projectData.filter( (cp:any) => cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) && (query.startDateFrom || query.startDateFromTo ? isDateInRange(cp.startDate, query.startDateFrom, query.startDateFromTo) : true) ), ); }} />
{t("Total Project Fee")}
${totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

{t("Total Invoiced Amount")}
${totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

{t("Total Received Amount")}
${totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

{t("Accounts Receivable")}
${receivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{t("Total Budget")}
${totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

{t("Total Cumulative Expenditure")}
${totalExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

{t("Remaining Budget")}
${expenditureReceivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
); }; export default ProjectCashFlow;