選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

1071 行
34 KiB

  1. "use client";
  2. import * as React from "react";
  3. import Grid from "@mui/material/Grid";
  4. import { useState, useEffect, useMemo } from "react";
  5. import Paper from "@mui/material/Paper";
  6. import { TFunction } from "i18next";
  7. import { useTranslation } from "react-i18next";
  8. import { Card, CardHeader } from "@mui/material";
  9. import CustomSearchForm from "../CustomSearchForm/CustomSearchForm";
  10. import CustomDatagrid from "../CustomDatagrid/CustomDatagrid";
  11. import ReactApexChart from "react-apexcharts";
  12. import { ApexOptions } from "apexcharts";
  13. import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
  14. import ReportProblemIcon from "@mui/icons-material/ReportProblem";
  15. import dynamic from "next/dynamic";
  16. import "../../app/global.css";
  17. import { AnyARecord, AnyCnameRecord } from "dns";
  18. import SearchBox, { Criterion } from "../SearchBox";
  19. import ProgressByClientSearch from "@/components/ProgressByClientSearch";
  20. import { Suspense } from "react";
  21. import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch";
  22. import { fetchProjectsCashFlow,fetchProjectsCashFlowMonthlyChart,fetchProjectsCashFlowReceivableAndExpenditure,fetchProjectsCashFlowAnticipate,fetchProjectsCashFlowLedger} from "@/app/api/cashflow";
  23. import { Input, Label } from "reactstrap";
  24. import { CashFlow } from "@/app/api/cashflow";
  25. import dayjs from 'dayjs';
  26. import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee";
  27. import Typography from "@mui/material/Typography";
  28. import { useSearchParams } from 'next/navigation';
  29. interface Props {
  30. projects: CashFlow[];
  31. }
  32. type SearchQuery = Partial<Omit<CashFlow, "id">>;
  33. type SearchParamNames = keyof SearchQuery;
  34. const ProjectCashFlow: React.FC = () => {
  35. const { t } = useTranslation("dashboard");
  36. const searchParams = useSearchParams();
  37. const projectId = searchParams.get('projectId');
  38. const todayDate = new Date();
  39. const [selectionModel, setSelectionModel]: any[] = useState<GridRowSelectionModel>([]);
  40. const [projectData, setProjectData]: any[] = React.useState([]);
  41. const [filteredResult, setFilteredResult]:any[] = useState([]);
  42. const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]);
  43. const [monthlyIncomeList, setMonthlyIncomeList]: any[] = React.useState([]);
  44. const [monthlyCumulativeIncomeList, setMonthlyCumulativeIncomeList]: any[] = React.useState([]);
  45. const [monthlyExpenditureList, setMonthlyExpenditureList]: any[] = React.useState([]);
  46. const [monthlyCumulativeExpenditureList, setMonthlyCumulativeExpenditureList]: any[] = React.useState([]);
  47. const [monthlyChartLeftMax, setMonthlyChartLeftMax]: any[] = React.useState(10);
  48. const [monthlyChartRightMax, setMonthlyChartRightMax]: any[] = React.useState(10);
  49. const [monthlyAnticipateLeftMax, setMonthlyAnticipateLeftMax]: any[] = React.useState(10);
  50. const [receivedPercentage,setReceivedPercentage]: any[] = React.useState(0);
  51. const [invoicedPercentage,setInvoicedPercentage]: any[] = React.useState(0);
  52. const [totalFee,setTotalFee]: any[] = React.useState(0);
  53. const [totalBudget,setTotalBudget]: any[] = React.useState(0);
  54. const [totalInvoiced,setTotalInvoiced]: any[] = React.useState(0);
  55. const [totalReceived,setTotalReceived]: any[] = React.useState(0);
  56. const [receivable,setReceivable]: any[] = React.useState(0);
  57. const [totalExpenditure,setTotalExpenditure]: any[] = React.useState(0);
  58. const [expenditureReceivable,setExpenditureReceivable]: any[] = React.useState(0);
  59. const [expenditurePercentage,setExpenditurePercentage]: any[] = React.useState(0);
  60. const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]);
  61. const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]);
  62. const [ledgerData, setLedgerData]: any[] = React.useState([]);
  63. const [isInitializing, setIsInitializing] = useState(true);
  64. const [cashFlowYear, setCashFlowYear]: any[] = React.useState(
  65. todayDate.getFullYear(),
  66. );
  67. const [anticipateCashFlowYear, setAnticipateCashFlowYear]: any[] = React.useState(
  68. todayDate.getFullYear(),
  69. );
  70. const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => {
  71. if (!isInitializing) {
  72. setSelectionModel(newSelectionModel);
  73. console.log(newSelectionModel)
  74. console.log(projectData)
  75. const selectedRowsData = projectData.filter((row: any) =>
  76. newSelectionModel.includes(row.id)
  77. );
  78. const projectIdList = selectedRowsData.map((row: any) => row.id);
  79. console.log(selectedRowsData)
  80. setSelectedProjectIdList(projectIdList);
  81. }
  82. };
  83. const fetchData = async () => {
  84. const cashFlowProject = await fetchProjectsCashFlow();
  85. console.log(cashFlowProject)
  86. setProjectData(cashFlowProject)
  87. setFilteredResult(cashFlowProject)
  88. }
  89. const fetchChartData = async () => {
  90. const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear);
  91. const monthlyIncome = []
  92. const cumulativeIncome = []
  93. const monthlyExpenditure = []
  94. const cumulativeExpenditure = []
  95. var leftMax = 0
  96. var rightMax = 0
  97. if (cashFlowMonthlyChartData.length !== 0) {
  98. for (var i = 0; i < cashFlowMonthlyChartData[0].incomeList.length; i++) {
  99. if (leftMax < cashFlowMonthlyChartData[0].incomeList[i].income || leftMax < cashFlowMonthlyChartData[0].expenditureList[i].expenditure){
  100. leftMax = Math.max(cashFlowMonthlyChartData[0].incomeList[i].income,cashFlowMonthlyChartData[0].expenditureList[i].expenditure)
  101. }
  102. monthlyIncome.push(cashFlowMonthlyChartData[0].incomeList[i].income)
  103. cumulativeIncome.push(cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome)
  104. }
  105. for (var i = 0; i < cashFlowMonthlyChartData[0].expenditureList.length; i++) {
  106. if (rightMax < cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome || rightMax < cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure){
  107. rightMax = Math.max(cashFlowMonthlyChartData[0].incomeList[i].cumulativeIncome,cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure)
  108. }
  109. monthlyExpenditure.push(cashFlowMonthlyChartData[0].expenditureList[i].expenditure)
  110. cumulativeExpenditure.push(cashFlowMonthlyChartData[0].expenditureList[i].cumulativeExpenditure)
  111. }
  112. setMonthlyIncomeList(monthlyIncome)
  113. setMonthlyCumulativeIncomeList(cumulativeIncome)
  114. setMonthlyExpenditureList(monthlyExpenditure)
  115. setMonthlyCumulativeExpenditureList(cumulativeExpenditure)
  116. setMonthlyChartLeftMax(leftMax)
  117. setMonthlyChartRightMax(rightMax)
  118. } else {
  119. setMonthlyIncomeList([0,0,0,0,0,0,0,0,0,0,0,0])
  120. setMonthlyCumulativeIncomeList([0,0,0,0,0,0,0,0,0,0,0,0])
  121. setMonthlyExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0])
  122. setMonthlyCumulativeExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0])
  123. }
  124. }
  125. const fetchReceivableAndExpenditureData = async () => {
  126. console.log("s2")
  127. if (selectedProjectIdList.length === 0) {
  128. setReceivedPercentage(0)
  129. setInvoicedPercentage(0)
  130. setTotalFee(0)
  131. setTotalInvoiced(0)
  132. setTotalReceived(0)
  133. setReceivable(0)
  134. setExpenditurePercentage(0)
  135. setTotalBudget(0)
  136. setTotalExpenditure(0)
  137. setExpenditureReceivable(0)
  138. } else {
  139. const cashFlowReceivableAndExpenditureData = await fetchProjectsCashFlowReceivableAndExpenditure(selectedProjectIdList);
  140. if(cashFlowReceivableAndExpenditureData.length !== 0){
  141. setReceivedPercentage(cashFlowReceivableAndExpenditureData[0].receivedPercentage)
  142. setInvoicedPercentage(cashFlowReceivableAndExpenditureData[0].invoicedPercentage)
  143. setTotalFee(cashFlowReceivableAndExpenditureData[0].totalProjectFee)
  144. setTotalInvoiced(cashFlowReceivableAndExpenditureData[0].totalInvoiced)
  145. setTotalReceived(cashFlowReceivableAndExpenditureData[0].totalReceived)
  146. setReceivable(cashFlowReceivableAndExpenditureData[0].receivable)
  147. setExpenditurePercentage(cashFlowReceivableAndExpenditureData[0].expenditurePercentage)
  148. setTotalBudget(cashFlowReceivableAndExpenditureData[0].totalBudget)
  149. setTotalExpenditure(cashFlowReceivableAndExpenditureData[0].totalExpenditure)
  150. setExpenditureReceivable(cashFlowReceivableAndExpenditureData[0].expenditureReceivable)
  151. }
  152. }
  153. }
  154. const fetchAnticipateData = async () => {
  155. const cashFlowAnticipateData = await fetchProjectsCashFlowAnticipate(selectedProjectIdList,anticipateCashFlowYear);
  156. const monthlyAnticipateIncome = []
  157. var anticipateLeftMax = 0
  158. if(cashFlowAnticipateData.length !== 0){
  159. for (var i = 0; i < cashFlowAnticipateData[0].anticipateIncomeList.length; i++) {
  160. if (anticipateLeftMax < cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome){
  161. anticipateLeftMax = Math.max(cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome,cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome)
  162. }
  163. monthlyAnticipateIncome.push(cashFlowAnticipateData[0].anticipateIncomeList[i].anticipateIncome)
  164. }
  165. setMonthlyAnticipateIncomeList(monthlyAnticipateIncome)
  166. } else {
  167. setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0])
  168. setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0])
  169. }
  170. if(cashFlowAnticipateData.length !== 0){
  171. if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) {
  172. const anticipateExpenditureList = []
  173. for (var i = 0; i < cashFlowAnticipateData[0].anticipateExpenditureList.length; i++) {
  174. const subAnticipateExpenditure = []
  175. var duration = cashFlowAnticipateData[0].anticipateExpenditureList[i].Duration
  176. var month = cashFlowAnticipateData[0].anticipateExpenditureList[i].startMonth
  177. const anticipateExpenditure = cashFlowAnticipateData[0].anticipateExpenditureList[i].aniticipateExpenditure
  178. for (var j = 1; j < 13; j++){
  179. if (month === j && duration > 0) {
  180. subAnticipateExpenditure.push(anticipateExpenditure)
  181. month = month + 1
  182. duration = duration - 1
  183. } else {
  184. subAnticipateExpenditure.push(0)
  185. }
  186. }
  187. anticipateExpenditureList.push(subAnticipateExpenditure)
  188. }
  189. const result = new Array(anticipateExpenditureList[0].length).fill(0);
  190. for (const arr of anticipateExpenditureList) {
  191. for (let i = 0; i < arr.length; i++) {
  192. result[i] += arr[i];
  193. }
  194. }
  195. setMonthlyAnticipateExpenditureList(result)
  196. for (var i = 0; i < monthlyAnticipateIncome.length; i++) {
  197. if (anticipateLeftMax < monthlyAnticipateIncome[i] || anticipateLeftMax < result[i]){
  198. anticipateLeftMax = Math.max(monthlyAnticipateIncome[i],result[i])
  199. }
  200. setMonthlyAnticipateLeftMax(anticipateLeftMax)
  201. }
  202. } else {
  203. setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0])
  204. }
  205. }
  206. }
  207. const fetchProjectCashFlowLedger = async () => {
  208. const cashFlowLedgerData = await fetchProjectsCashFlowLedger(selectedProjectIdList);
  209. setLedgerData(cashFlowLedgerData)
  210. }
  211. useEffect(() => {
  212. if (projectId !== null) {
  213. setSelectedProjectIdList([parseInt(projectId)])
  214. setSelectionModel([parseInt(projectId)]);
  215. }
  216. fetchData().then(() => {
  217. setIsInitializing(false);
  218. });
  219. }, []);
  220. useEffect(() => {
  221. fetchChartData();
  222. fetchReceivableAndExpenditureData();
  223. fetchAnticipateData();
  224. fetchProjectCashFlowLedger();
  225. }, [cashFlowYear,selectedProjectIdList]);
  226. useEffect(() => {
  227. fetchAnticipateData();
  228. }, [anticipateCashFlowYear,selectedProjectIdList]);
  229. const columns = [
  230. {
  231. id: "projectCode",
  232. field: "projectCode",
  233. headerName: t("Project Code"),
  234. flex: 1,
  235. },
  236. {
  237. id: "projectName",
  238. field: "projectName",
  239. headerName: t("Project Name"),
  240. flex: 1,
  241. },
  242. {
  243. id: "team",
  244. field: "team",
  245. headerName: t("Team"),
  246. flex: 1,
  247. },
  248. {
  249. id: "teamLead",
  250. field: "teamLead",
  251. headerName: t("Team Leader"),
  252. flex: 1,
  253. },
  254. {
  255. id: "startDate",
  256. field: "startDate",
  257. headerName: t("Start Date"),
  258. flex: 1,
  259. },
  260. {
  261. id: "targetEndDate",
  262. field: "targetEndDate",
  263. headerName: t("Target End Date"),
  264. flex: 1,
  265. },
  266. {
  267. id: "client",
  268. field: "client",
  269. headerName: t("Client"),
  270. flex: 1,
  271. },
  272. {
  273. id: "subsidiary",
  274. field: "subsidiary",
  275. headerName: t("Subsidiary"),
  276. flex: 1,
  277. },
  278. ];
  279. const ledgerColumns = [
  280. {
  281. id: "date",
  282. field: "date",
  283. headerName: t("Date"),
  284. flex: 0.5,
  285. },
  286. {
  287. id: "expenditure",
  288. field: "expenditure",
  289. headerName: t("Expenditure (HKD)"),
  290. flex: 0.6,
  291. renderCell: (params:any) => {
  292. return (
  293. <span>${params.row.expenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  294. )
  295. }
  296. },
  297. {
  298. id: "income",
  299. field: "income",
  300. headerName: t("Income (HKD)"),
  301. flex: 0.6,
  302. renderCell: (params:any) => {
  303. return (
  304. <span>${params.row.income.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  305. )
  306. }
  307. },
  308. {
  309. id: "balance",
  310. field: "balance",
  311. headerName: t("Cash Flow Balance (HKD)"),
  312. flex: 0.6,
  313. renderCell: (params:any) => {
  314. if (params.row.balance < 0) {
  315. return (
  316. <span>(${Math.abs(params.row.balance).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })})</span>
  317. )
  318. } else {
  319. return (
  320. <span>${params.row.balance.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  321. )
  322. }
  323. },
  324. },
  325. {
  326. id: "remarks",
  327. field: "remarks",
  328. headerName: t("Remarks"),
  329. flex: 1,
  330. },
  331. ];
  332. const options: ApexOptions = {
  333. tooltip: {
  334. y: {
  335. formatter: function(val) {
  336. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  337. }
  338. }
  339. },
  340. chart: {
  341. height: 350,
  342. type: "line",
  343. },
  344. stroke: {
  345. width: [0, 0, 2, 2],
  346. },
  347. plotOptions: {
  348. bar: {
  349. horizontal: false,
  350. distributed: false,
  351. },
  352. },
  353. dataLabels: {
  354. enabled: false,
  355. },
  356. xaxis: {
  357. categories: [
  358. t("JAN"),
  359. t("FEB"),
  360. t("MAR"),
  361. t("APR"),
  362. t("MAY"),
  363. t("JUN"),
  364. t("JUL"),
  365. t("AUG"),
  366. t("SEP"),
  367. t("OCT"),
  368. t("NOV"),
  369. t("DEC"),
  370. ],
  371. },
  372. yaxis: [
  373. {
  374. title: {
  375. text: t("Monthly Income and Expenditure (HKD)"),
  376. style: {
  377. fontSize: "13px",
  378. }
  379. },
  380. min: 0,
  381. max: monthlyChartLeftMax,
  382. tickAmount: 5,
  383. labels: {
  384. formatter: function (val) {
  385. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  386. }
  387. }
  388. },
  389. {
  390. show: false,
  391. seriesName: "Monthly Expenditure",
  392. title: {
  393. text: "Monthly Expenditure (HKD)",
  394. },
  395. min: 0,
  396. max: monthlyChartLeftMax,
  397. tickAmount: 5,
  398. },
  399. {
  400. seriesName: "Cumulative Income",
  401. opposite: true,
  402. title: {
  403. text: t("Cumulative Income and Expenditure (HKD)"),
  404. style: {
  405. fontSize: "13px",
  406. }
  407. },
  408. min: 0,
  409. max: monthlyChartRightMax,
  410. tickAmount: 5,
  411. labels: {
  412. formatter: function (val) {
  413. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  414. }
  415. }
  416. },
  417. {
  418. show: false,
  419. seriesName: "Cumulative Expenditure",
  420. opposite: true,
  421. title: {
  422. text: "Cumulative Expenditure (HKD)",
  423. },
  424. min: 0,
  425. max: monthlyChartRightMax,
  426. tickAmount: 5,
  427. },
  428. ],
  429. grid: {
  430. borderColor: "#f1f1f1",
  431. },
  432. annotations: {},
  433. series: [
  434. {
  435. name: t("Monthly Income"),
  436. type: "column",
  437. color: "#ffde91",
  438. data: monthlyIncomeList,
  439. },
  440. {
  441. name: t("Monthly Expenditure"),
  442. type: "column",
  443. color: "#82b59a",
  444. data: monthlyExpenditureList,
  445. },
  446. {
  447. name: t("Cumulative Income"),
  448. type: "line",
  449. color: "#EE6D7A",
  450. data: monthlyCumulativeIncomeList,
  451. },
  452. {
  453. name: t("Cumulative Expenditure"),
  454. type: "line",
  455. color: "#7cd3f2",
  456. data: monthlyCumulativeExpenditureList,
  457. },
  458. ],
  459. };
  460. const anticipateOptions: ApexOptions = {
  461. tooltip: {
  462. y: {
  463. formatter: function(val) {
  464. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  465. }
  466. }
  467. },
  468. chart: {
  469. height: 350,
  470. type: "line",
  471. },
  472. stroke: {
  473. width: [0, 0, 2, 2],
  474. },
  475. plotOptions: {
  476. bar: {
  477. horizontal: false,
  478. distributed: false,
  479. },
  480. },
  481. dataLabels: {
  482. enabled: false,
  483. },
  484. xaxis: {
  485. categories: [
  486. t("JAN"),
  487. t("FEB"),
  488. t("MAR"),
  489. t("APR"),
  490. t("MAY"),
  491. t("JUN"),
  492. t("JUL"),
  493. t("AUG"),
  494. t("SEP"),
  495. t("OCT"),
  496. t("NOV"),
  497. t("DEC"),
  498. ],
  499. },
  500. yaxis: [
  501. {
  502. title: {
  503. text: t("Anticipate Monthly Income and Expenditure") + t("HKD"),
  504. style: {
  505. fontSize: "13px",
  506. }
  507. },
  508. min: 0,
  509. max: monthlyAnticipateLeftMax,
  510. tickAmount: 5,
  511. labels: {
  512. formatter: function (val) {
  513. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  514. }
  515. }
  516. },
  517. ],
  518. grid: {
  519. borderColor: "#f1f1f1",
  520. },
  521. annotations: {},
  522. series: [
  523. {
  524. name: t("Monthly Income"),
  525. type: "column",
  526. color: "#f1c48a",
  527. data: monthlyAnticipateIncomeList,
  528. },
  529. {
  530. name: t("Monthly Expenditure"),
  531. type: "column",
  532. color: "#89d7f3",
  533. data: monthlyAnticipateExpenditureList,
  534. }
  535. ],
  536. };
  537. const accountsReceivableOptions: ApexOptions = {
  538. colors: ["#20E647"],
  539. series: [receivedPercentage,invoicedPercentage],
  540. chart: {
  541. height: 50,
  542. type: "radialBar",
  543. },
  544. plotOptions: {
  545. radialBar: {
  546. hollow: {
  547. size: "70%",
  548. background: "#ffffff",
  549. },
  550. track: {
  551. dropShadow: {
  552. enabled: true,
  553. top: 2,
  554. left: 0,
  555. blur: 4,
  556. opacity: 0.15,
  557. },
  558. },
  559. dataLabels: {
  560. name: {
  561. show: true,
  562. fontSize: "0.9em",
  563. },
  564. value: {
  565. color: "#3e98c7",
  566. fontSize: "1.5em",
  567. show: true,
  568. },
  569. total: {
  570. show: true,
  571. color: "#20E647",
  572. fontSize: "0.9em",
  573. label: t('Receivable') + " / " + t('Invoiced'),
  574. formatter: function (w:any) {
  575. return receivedPercentage + "% / " + invoicedPercentage + "%"
  576. },
  577. }
  578. },
  579. },
  580. },
  581. fill: {
  582. type: "gradient",
  583. gradient: {
  584. shade: "dark",
  585. type: "vertical",
  586. gradientToColors: ["#87D4F9"],
  587. stops: [0, 100],
  588. },
  589. },
  590. stroke: {
  591. lineCap: "round",
  592. },
  593. labels: ["Received Amount","Invoiced Amount"],
  594. };
  595. const expenditureOptions: ApexOptions = {
  596. colors: ["#20E647"],
  597. series: [expenditurePercentage],
  598. chart: {
  599. height: 350,
  600. type: "radialBar",
  601. },
  602. plotOptions: {
  603. radialBar: {
  604. hollow: {
  605. size: "70%",
  606. background: "#ffffff",
  607. },
  608. track: {
  609. dropShadow: {
  610. enabled: true,
  611. top: 2,
  612. left: 0,
  613. blur: 4,
  614. opacity: 0.15,
  615. },
  616. },
  617. dataLabels: {
  618. name: {
  619. show: true,
  620. fontSize: "0.9em",
  621. },
  622. value: {
  623. color: "#3e98c7",
  624. fontSize: "1.5em",
  625. show: true,
  626. },
  627. },
  628. },
  629. },
  630. fill: {
  631. type: "gradient",
  632. gradient: {
  633. shade: "dark",
  634. type: "vertical",
  635. gradientToColors: ["#87D4F9"],
  636. stops: [0, 100],
  637. },
  638. },
  639. stroke: {
  640. lineCap: "round",
  641. },
  642. labels: [t("Expenditure")],
  643. };
  644. const rows = [
  645. {
  646. id: 1,
  647. projectCode: "M1001",
  648. projectName: "Consultancy Project A",
  649. team: "XXX",
  650. teamLeader: "XXX",
  651. startDate: "01/07/2022",
  652. targetEndDate: "01/04/2024",
  653. client: "Client B",
  654. subsidiary: "N/A",
  655. },
  656. {
  657. id: 2,
  658. projectCode: "M1301",
  659. projectName: "Consultancy Project AAAA",
  660. team: "XXX",
  661. teamLeader: "XXX",
  662. startDate: "01/09/2022",
  663. targetEndDate: "20/02/2024",
  664. client: "Client C",
  665. subsidiary: "Subsidiary A",
  666. },
  667. {
  668. id: 3,
  669. projectCode: "M1354",
  670. projectName: "Consultancy Project BBB",
  671. team: "YYY",
  672. teamLeader: "YYY",
  673. startDate: "01/02/2023",
  674. targetEndDate: "31/01/2024",
  675. client: "Client D",
  676. subsidiary: "Subsidiary C",
  677. },
  678. ];
  679. const ledgerRows = [
  680. {
  681. id: 1,
  682. date: "Feb 2023",
  683. expenditure: "-",
  684. income: "100,000.00",
  685. cashFlowBalance: "100,000.00",
  686. remarks: "Payment Milestone 1 (10%)",
  687. },
  688. {
  689. id: 2,
  690. date: "Feb 2023",
  691. expenditure: "160,000.00",
  692. income: "-",
  693. cashFlowBalance: "(60,000.00)",
  694. remarks: "Monthly Manpower Expenditure",
  695. },
  696. {
  697. id: 3,
  698. date: "Mar 2023",
  699. expenditure: "160,000.00",
  700. income: "-",
  701. cashFlowBalance: "(180,000.00)",
  702. remarks: "Monthly Manpower Expenditure",
  703. },
  704. {
  705. id: 4,
  706. date: "Apr 2023",
  707. expenditure: "120,000.00",
  708. income: "-",
  709. cashFlowBalance: "(300,000.00)",
  710. remarks: "Monthly Manpower Expenditure",
  711. },
  712. {
  713. id: 5,
  714. date: "May 2023",
  715. expenditure: "-",
  716. income: "200,000.00",
  717. cashFlowBalance: "(100,000.00)",
  718. remarks: "Payment Milestone 2 (20%)",
  719. },
  720. {
  721. id: 6,
  722. date: "May 2023",
  723. expenditure: "40,000.00",
  724. income: "-",
  725. cashFlowBalance: "(140,000.00)",
  726. remarks: "Monthly Manpower Expenditure",
  727. },
  728. ];
  729. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  730. () => [
  731. { label: t("Project Code"), paramName: "projectCode", type: "text" },
  732. { label: t("Project Name"), paramName: "projectName", type: "text" },
  733. {
  734. label: t("Start Date From"),
  735. label2: t("Start Date To"),
  736. paramName: "startDateFrom",
  737. type: "dateRange",
  738. },
  739. ],
  740. [t],
  741. );
  742. function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean {
  743. console.log(startDate)
  744. console.log(endDate)
  745. if (!startDate || !endDate) {
  746. return false;
  747. }
  748. const dateToCheckObj = new Date(dateToCheck);
  749. const startDateObj = new Date(startDate);
  750. const endDateObj = new Date(endDate);
  751. console.log(dateToCheckObj)
  752. return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj;
  753. }
  754. return (
  755. <>
  756. {/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
  757. <ProgressCashFlowSearch />
  758. </Suspense> */}
  759. <Typography variant="h4" marginInlineEnd={2}>
  760. {t('Project Cash Flow')}
  761. </Typography>
  762. <SearchBox
  763. criteria={searchCriteria}
  764. onSearch={(query) => {
  765. console.log(query)
  766. setFilteredResult(
  767. projectData.filter(
  768. (cp:any) =>
  769. cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
  770. cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) &&
  771. (query.startDateFrom || query.startDateFromTo
  772. ? isDateInRange(cp.startDate, query.startDateFrom, query.startDateFromTo)
  773. : true)
  774. ),
  775. );
  776. }}
  777. />
  778. <CustomDatagrid
  779. rows={filteredResult}
  780. columns={columns}
  781. columnWidth={200}
  782. dataGridHeight={300}
  783. checkboxSelection={true}
  784. onRowSelectionModelChange={handleSelectionChange}
  785. rowSelectionModel={selectionModel}
  786. />
  787. <Grid item sm>
  788. <div style={{ display: "inline-block", width: "50%" }}>
  789. <Grid item xs={12} md={12} lg={12}>
  790. <Card>
  791. <CardHeader
  792. className="text-slate-500"
  793. title= {t("Project Cash Flow by Month")}
  794. />
  795. <div style={{ display: "inline-block", width: "99%" }}>
  796. <div className="inline-block">
  797. <Label className="text-slate-500 font-medium ml-6">
  798. {t("Year")}:&nbsp;
  799. </Label>
  800. <Input
  801. id={"cashFlowYear"}
  802. value={cashFlowYear}
  803. readOnly={true}
  804. bsSize="lg"
  805. className="rounded-md text-base w-12"
  806. />
  807. </div>
  808. <div className="inline-block ml-1">
  809. <button
  810. onClick={() => setCashFlowYear(cashFlowYear - 1)}
  811. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  812. >
  813. &lt;
  814. </button>
  815. </div>
  816. <div className="inline-block ml-1">
  817. <button
  818. onClick={() => setCashFlowYear(cashFlowYear + 1)}
  819. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  820. >
  821. &gt;
  822. </button>
  823. </div>
  824. <ReactApexChart
  825. options={options}
  826. series={options.series}
  827. type="line"
  828. height="auto"
  829. />
  830. </div>
  831. </Card>
  832. </Grid>
  833. </div>
  834. <div
  835. style={{
  836. display: "inline-block",
  837. width: "24%",
  838. verticalAlign: "top",
  839. marginLeft: 10,
  840. }}
  841. >
  842. <Grid item xs={12} md={12} lg={12}>
  843. <Card>
  844. <CardHeader
  845. className="text-slate-500"
  846. title= {t("Accounts Receivable (HKD)")}
  847. />
  848. <div style={{ display: "inline-block", width: "99%" }}>
  849. <ReactApexChart
  850. options={accountsReceivableOptions}
  851. series={accountsReceivableOptions.series}
  852. type="radialBar"
  853. />
  854. </div>
  855. <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100">
  856. <div
  857. className="text-sm font-medium ml-5 mt-2"
  858. style={{ color: "#898d8d" }}
  859. >
  860. {t("Total Project Fee")}
  861. </div>
  862. <div
  863. className="text-lg font-medium ml-5"
  864. style={{ color: "#6b87cf" }}
  865. >
  866. ${totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  867. </div>
  868. <hr />
  869. <div
  870. className="text-sm font-medium ml-5 mt-2"
  871. style={{ color: "#898d8d" }}
  872. >
  873. {t("Total Invoiced Amount")}
  874. </div>
  875. <div
  876. className="text-lg font-medium ml-5"
  877. style={{ color: "#6b87cf" }}
  878. >
  879. ${totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  880. </div>
  881. <hr />
  882. <div
  883. className="text-sm font-medium ml-5"
  884. style={{ color: "#898d8d" }}
  885. >
  886. {t("Total Received Amount")}
  887. </div>
  888. <div
  889. className="text-lg font-medium ml-5"
  890. style={{ color: "#6b87cf" }}
  891. >
  892. ${totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  893. </div>
  894. <hr />
  895. <div
  896. className="text-sm font-medium ml-5"
  897. style={{ color: "#898d8d" }}
  898. >
  899. {t("Accounts Receivable")}
  900. </div>
  901. <div
  902. className="text-lg font-medium ml-5 mb-2"
  903. style={{ color: "#6b87cf" }}
  904. >
  905. ${receivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  906. </div>
  907. </Card>
  908. </Card>
  909. </Grid>
  910. </div>
  911. <div
  912. style={{
  913. display: "inline-block",
  914. width: "24%",
  915. verticalAlign: "top",
  916. marginLeft: 10,
  917. }}
  918. >
  919. <Grid item xs={12} md={12} lg={12}>
  920. <Card>
  921. <CardHeader
  922. className="text-slate-500"
  923. title={t("Expenditure (HKD)")}
  924. />
  925. <ReactApexChart
  926. options={expenditureOptions}
  927. series={expenditureOptions.series}
  928. type="radialBar"
  929. />
  930. <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100">
  931. <div
  932. className="text-sm font-medium ml-5 mt-2"
  933. style={{ color: "#898d8d" }}
  934. >
  935. {t("Total Budget")}
  936. </div>
  937. <div
  938. className="text-lg font-medium ml-5"
  939. style={{ color: "#6b87cf" }}
  940. >
  941. ${totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  942. </div>
  943. <hr />
  944. <div
  945. className="text-sm font-medium ml-5"
  946. style={{ color: "#898d8d" }}
  947. >
  948. {t("Total Cumulative Expenditure")}
  949. </div>
  950. <div
  951. className="text-lg font-medium ml-5"
  952. style={{ color: "#6b87cf" }}
  953. >
  954. ${totalExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  955. </div>
  956. <hr />
  957. <div
  958. className="text-sm font-medium ml-5"
  959. style={{ color: "#898d8d" }}
  960. >
  961. {t("Remaining Budget")}
  962. </div>
  963. <div
  964. className="text-lg font-medium ml-5 mb-2"
  965. style={{ color: "#6b87cf" }}
  966. >
  967. ${expenditureReceivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  968. </div>
  969. </Card>
  970. </Card>
  971. </Grid>
  972. </div>
  973. <div
  974. className="mt-5"
  975. style={{
  976. display: "inline-block",
  977. width: "100%",
  978. verticalAlign: "top",
  979. }}
  980. >
  981. <Grid item xs={12} md={12} lg={12}>
  982. <Card>
  983. <CardHeader
  984. className="text-slate-500"
  985. title= {t("Anticipate Cash Flow by Month")}
  986. />
  987. <div style={{ display: "inline-block", width: "99%" }}>
  988. <div className="inline-block">
  989. <Label className="text-slate-500 font-medium ml-6">
  990. {t("Year")}:&nbsp;
  991. </Label>
  992. <Input
  993. id={"cashFlowYear"}
  994. value={anticipateCashFlowYear}
  995. readOnly={true}
  996. bsSize="lg"
  997. className="rounded-md text-base w-12"
  998. />
  999. </div>
  1000. <div className="inline-block ml-1">
  1001. <button
  1002. onClick={() => setAnticipateCashFlowYear(anticipateCashFlowYear - 1)}
  1003. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  1004. >
  1005. &lt;
  1006. </button>
  1007. </div>
  1008. <div className="inline-block ml-1">
  1009. <button
  1010. onClick={() => setAnticipateCashFlowYear(anticipateCashFlowYear + 1)}
  1011. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  1012. >
  1013. &gt;
  1014. </button>
  1015. </div>
  1016. <ReactApexChart
  1017. options={anticipateOptions}
  1018. series={anticipateOptions.series}
  1019. type="line"
  1020. height="350"
  1021. />
  1022. </div>
  1023. </Card>
  1024. <Card>
  1025. <CardHeader
  1026. className="text-slate-500"
  1027. title= {t("Cash Flow Ledger by Month")}
  1028. />
  1029. <div className="ml-4 mr-4">
  1030. <CustomDatagrid
  1031. rows={ledgerData}
  1032. columns={ledgerColumns}
  1033. columnWidth={200}
  1034. dataGridHeight={300}
  1035. />
  1036. </div>
  1037. </Card>
  1038. </Grid>
  1039. </div>
  1040. </Grid>
  1041. </>
  1042. );
  1043. };
  1044. export default ProjectCashFlow;