您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

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