Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 

1111 рядки
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("Manpower Spent(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: "expense",
  302. field: "expense",
  303. headerName: t("Expense (HKD)"),
  304. flex: 0.6,
  305. type: "number",
  306. renderCell: (params:any) => {
  307. return (
  308. <span>${params.row.expense.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  309. )
  310. }
  311. },
  312. {
  313. id: "income",
  314. field: "income",
  315. headerName: t("Income (HKD)"),
  316. flex: 0.6,
  317. type: "number",
  318. renderCell: (params:any) => {
  319. return (
  320. <span>${params.row.income.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  321. )
  322. }
  323. },
  324. {
  325. id: "balance",
  326. field: "balance",
  327. headerName: t("Cash Flow Balance (HKD)"),
  328. flex: 0.6,
  329. type: "number",
  330. renderCell: (params:any) => {
  331. if (params.row.balance < 0) {
  332. return (
  333. <span>(${Math.abs(params.row.balance).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })})</span>
  334. )
  335. } else {
  336. return (
  337. <span>${params.row.balance.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
  338. )
  339. }
  340. },
  341. },
  342. {
  343. id: "remarks",
  344. field: "remarks",
  345. headerName: t("Remarks"),
  346. flex: 1,
  347. },
  348. ];
  349. const options: ApexOptions = {
  350. tooltip: {
  351. y: {
  352. formatter: function(val) {
  353. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  354. }
  355. }
  356. },
  357. chart: {
  358. height: 350,
  359. type: "line",
  360. },
  361. stroke: {
  362. width: [0, 0, 2, 2],
  363. },
  364. plotOptions: {
  365. bar: {
  366. horizontal: false,
  367. distributed: false,
  368. },
  369. },
  370. dataLabels: {
  371. enabled: false,
  372. },
  373. xaxis: {
  374. categories: [
  375. t("JAN"),
  376. t("FEB"),
  377. t("MAR"),
  378. t("APR"),
  379. t("MAY"),
  380. t("JUN"),
  381. t("JUL"),
  382. t("AUG"),
  383. t("SEP"),
  384. t("OCT"),
  385. t("NOV"),
  386. t("DEC"),
  387. ],
  388. },
  389. yaxis: [
  390. {
  391. title: {
  392. text: t("Monthly Income and Expenditure (HKD)"),
  393. style: {
  394. fontSize: "13px",
  395. }
  396. },
  397. min: 0,
  398. max: monthlyChartLeftMax,
  399. tickAmount: 5,
  400. labels: {
  401. formatter: function (val) {
  402. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  403. }
  404. }
  405. },
  406. {
  407. show: false,
  408. seriesName: "Monthly Expenditure",
  409. title: {
  410. text: "Monthly Expenditure (HKD)",
  411. },
  412. min: 0,
  413. max: monthlyChartLeftMax,
  414. tickAmount: 5,
  415. },
  416. {
  417. seriesName: "Cumulative Income",
  418. opposite: true,
  419. title: {
  420. text: t("Cumulative Income and Expenditure (HKD)"),
  421. style: {
  422. fontSize: "13px",
  423. }
  424. },
  425. min: 0,
  426. max: monthlyChartRightMax,
  427. tickAmount: 5,
  428. labels: {
  429. formatter: function (val) {
  430. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  431. }
  432. }
  433. },
  434. {
  435. show: false,
  436. seriesName: "Cumulative Expenditure",
  437. opposite: true,
  438. title: {
  439. text: "Cumulative Expenditure (HKD)",
  440. },
  441. min: 0,
  442. max: monthlyChartRightMax,
  443. tickAmount: 5,
  444. },
  445. ],
  446. grid: {
  447. borderColor: "#f1f1f1",
  448. },
  449. annotations: {},
  450. series: [
  451. {
  452. name: t("Monthly Income"),
  453. type: "column",
  454. color: "#ffde91",
  455. data: monthlyIncomeList,
  456. },
  457. {
  458. name: t("Monthly Expenditure"),
  459. type: "column",
  460. color: "#82b59a",
  461. data: monthlyExpenditureList,
  462. },
  463. {
  464. name: t("Cumulative Income"),
  465. type: "line",
  466. color: "#EE6D7A",
  467. data: monthlyCumulativeIncomeList,
  468. },
  469. {
  470. name: t("Cumulative Expenditure"),
  471. type: "line",
  472. color: "#7cd3f2",
  473. data: monthlyCumulativeExpenditureList,
  474. },
  475. ],
  476. };
  477. const anticipateOptions: ApexOptions = {
  478. tooltip: {
  479. y: {
  480. formatter: function(val) {
  481. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  482. }
  483. }
  484. },
  485. chart: {
  486. height: 350,
  487. type: "line",
  488. },
  489. stroke: {
  490. width: [0, 0, 2, 2],
  491. },
  492. plotOptions: {
  493. bar: {
  494. horizontal: false,
  495. distributed: false,
  496. },
  497. },
  498. dataLabels: {
  499. enabled: false,
  500. },
  501. xaxis: {
  502. categories: [
  503. t("JAN"),
  504. t("FEB"),
  505. t("MAR"),
  506. t("APR"),
  507. t("MAY"),
  508. t("JUN"),
  509. t("JUL"),
  510. t("AUG"),
  511. t("SEP"),
  512. t("OCT"),
  513. t("NOV"),
  514. t("DEC"),
  515. ],
  516. },
  517. yaxis: [
  518. {
  519. title: {
  520. text: t("Anticipate Monthly Income and Expenditure") + t("HKD"),
  521. style: {
  522. fontSize: "13px",
  523. }
  524. },
  525. min: 0,
  526. max: monthlyAnticipateLeftMax,
  527. tickAmount: 5,
  528. labels: {
  529. formatter: function (val) {
  530. return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  531. }
  532. }
  533. },
  534. ],
  535. grid: {
  536. borderColor: "#f1f1f1",
  537. },
  538. annotations: {},
  539. series: [
  540. {
  541. name: t("Monthly Income"),
  542. type: "column",
  543. color: "#f1c48a",
  544. data: monthlyAnticipateIncomeList,
  545. },
  546. {
  547. name: t("Monthly Expenditure"),
  548. type: "column",
  549. color: "#89d7f3",
  550. data: monthlyAnticipateExpenditureList,
  551. }
  552. ],
  553. };
  554. const accountsReceivableOptions: ApexOptions = {
  555. colors: ["#20E647"],
  556. series: [receivedPercentage,invoicedPercentage],
  557. chart: {
  558. height: 50,
  559. type: "radialBar",
  560. },
  561. plotOptions: {
  562. radialBar: {
  563. hollow: {
  564. size: "70%",
  565. background: "#ffffff",
  566. },
  567. track: {
  568. dropShadow: {
  569. enabled: true,
  570. top: 2,
  571. left: 0,
  572. blur: 4,
  573. opacity: 0.15,
  574. },
  575. },
  576. dataLabels: {
  577. name: {
  578. show: true,
  579. fontSize: "0.9em",
  580. },
  581. value: {
  582. color: "#3e98c7",
  583. fontSize: "1.5em",
  584. show: true,
  585. },
  586. total: {
  587. show: true,
  588. color: "#20E647",
  589. fontSize: "0.9em",
  590. label: t('Receivable') + " / " + t('Invoiced'),
  591. formatter: function (w:any) {
  592. return receivedPercentage + "% / " + invoicedPercentage + "%"
  593. },
  594. }
  595. },
  596. },
  597. },
  598. fill: {
  599. type: "gradient",
  600. gradient: {
  601. shade: "dark",
  602. type: "vertical",
  603. gradientToColors: ["#87D4F9"],
  604. stops: [0, 100],
  605. },
  606. },
  607. stroke: {
  608. lineCap: "round",
  609. },
  610. labels: ["Received Amount","Invoiced Amount"],
  611. };
  612. const expenditureOptions: ApexOptions = {
  613. colors: ["#20E647"],
  614. series: [expenditurePercentage],
  615. chart: {
  616. height: 350,
  617. type: "radialBar",
  618. },
  619. plotOptions: {
  620. radialBar: {
  621. hollow: {
  622. size: "70%",
  623. background: "#ffffff",
  624. },
  625. track: {
  626. dropShadow: {
  627. enabled: true,
  628. top: 2,
  629. left: 0,
  630. blur: 4,
  631. opacity: 0.15,
  632. },
  633. },
  634. dataLabels: {
  635. name: {
  636. show: true,
  637. fontSize: "0.9em",
  638. },
  639. value: {
  640. color: "#3e98c7",
  641. fontSize: "1.5em",
  642. show: true,
  643. },
  644. },
  645. },
  646. },
  647. fill: {
  648. type: "gradient",
  649. gradient: {
  650. shade: "dark",
  651. type: "vertical",
  652. gradientToColors: ["#87D4F9"],
  653. stops: [0, 100],
  654. },
  655. },
  656. stroke: {
  657. lineCap: "round",
  658. },
  659. labels: [t("Expenditure")],
  660. };
  661. const rows = [
  662. {
  663. id: 1,
  664. projectCode: "M1001",
  665. projectName: "Consultancy Project A",
  666. team: "XXX",
  667. teamLeader: "XXX",
  668. startDate: "01/07/2022",
  669. targetEndDate: "01/04/2024",
  670. client: "Client B",
  671. subsidiary: "N/A",
  672. },
  673. {
  674. id: 2,
  675. projectCode: "M1301",
  676. projectName: "Consultancy Project AAAA",
  677. team: "XXX",
  678. teamLeader: "XXX",
  679. startDate: "01/09/2022",
  680. targetEndDate: "20/02/2024",
  681. client: "Client C",
  682. subsidiary: "Subsidiary A",
  683. },
  684. {
  685. id: 3,
  686. projectCode: "M1354",
  687. projectName: "Consultancy Project BBB",
  688. team: "YYY",
  689. teamLeader: "YYY",
  690. startDate: "01/02/2023",
  691. targetEndDate: "31/01/2024",
  692. client: "Client D",
  693. subsidiary: "Subsidiary C",
  694. },
  695. ];
  696. const ledgerRows = [
  697. {
  698. id: 1,
  699. date: "Feb 2023",
  700. expenditure: "-",
  701. income: "100,000.00",
  702. cashFlowBalance: "100,000.00",
  703. remarks: "Payment Milestone 1 (10%)",
  704. },
  705. {
  706. id: 2,
  707. date: "Feb 2023",
  708. expenditure: "160,000.00",
  709. income: "-",
  710. cashFlowBalance: "(60,000.00)",
  711. remarks: "Monthly Manpower Expenditure",
  712. },
  713. {
  714. id: 3,
  715. date: "Mar 2023",
  716. expenditure: "160,000.00",
  717. income: "-",
  718. cashFlowBalance: "(180,000.00)",
  719. remarks: "Monthly Manpower Expenditure",
  720. },
  721. {
  722. id: 4,
  723. date: "Apr 2023",
  724. expenditure: "120,000.00",
  725. income: "-",
  726. cashFlowBalance: "(300,000.00)",
  727. remarks: "Monthly Manpower Expenditure",
  728. },
  729. {
  730. id: 5,
  731. date: "May 2023",
  732. expenditure: "-",
  733. income: "200,000.00",
  734. cashFlowBalance: "(100,000.00)",
  735. remarks: "Payment Milestone 2 (20%)",
  736. },
  737. {
  738. id: 6,
  739. date: "May 2023",
  740. expenditure: "40,000.00",
  741. income: "-",
  742. cashFlowBalance: "(140,000.00)",
  743. remarks: "Monthly Manpower Expenditure",
  744. },
  745. ];
  746. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  747. () => [
  748. { label: t("Project Code"), paramName: "projectCode", type: "text" },
  749. { label: t("Project Name"), paramName: "projectName", type: "text" },
  750. {
  751. label: t("Start Date From"),
  752. label2: t("Start Date To"),
  753. paramName: "startDateFrom",
  754. type: "dateRange",
  755. },
  756. ],
  757. [t],
  758. );
  759. function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean {
  760. if (!startDate || !endDate) {
  761. return false;
  762. }
  763. const dateToCheckObj = new Date(dateToCheck);
  764. const startDateObj = new Date(startDate);
  765. const endDateObj = new Date(endDate);
  766. return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj;
  767. }
  768. return (
  769. <>
  770. {/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
  771. <ProgressCashFlowSearch />
  772. </Suspense> */}
  773. <Typography variant="h4" marginInlineEnd={2}>
  774. {t('Project Cash Flow')}
  775. </Typography>
  776. <SearchBox
  777. criteria={searchCriteria}
  778. onSearch={(query) => {
  779. setFilteredResult(
  780. projectData.filter(
  781. (cp:any) =>
  782. cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
  783. cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) &&
  784. (query.startDateFrom || query.startDateFromTo
  785. ? isDateInRange(cp.startDate, query.startDateFrom, query.startDateFromTo)
  786. : true)
  787. ),
  788. );
  789. }}
  790. />
  791. <CustomDatagrid
  792. rows={filteredResult}
  793. columns={columns}
  794. columnWidth={200}
  795. dataGridHeight={300}
  796. checkboxSelection={true}
  797. onRowSelectionModelChange={handleSelectionChange}
  798. rowSelectionModel={selectionModel}
  799. />
  800. <Grid item sm>
  801. <div style={{ display: "inline-block", width: "50%" }}>
  802. <Grid item xs={12} md={12} lg={12}>
  803. <Card>
  804. <CardHeader
  805. className="text-slate-500"
  806. title= {t("Project Cash Flow by Month")}
  807. />
  808. <div style={{ display: "inline-block", width: "99%" }}>
  809. <div className="inline-block">
  810. <Label className="text-slate-500 font-medium ml-6">
  811. {t("Year")}:&nbsp;
  812. </Label>
  813. <Input
  814. id={"cashFlowYear"}
  815. value={cashFlowYear}
  816. readOnly={true}
  817. bsSize="lg"
  818. className="rounded-md text-base w-12"
  819. />
  820. </div>
  821. <div className="inline-block ml-1">
  822. <button
  823. onClick={() => setCashFlowYear(cashFlowYear - 1)}
  824. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  825. >
  826. &lt;
  827. </button>
  828. </div>
  829. <div className="inline-block ml-1">
  830. <button
  831. onClick={() => setCashFlowYear(cashFlowYear + 1)}
  832. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  833. >
  834. &gt;
  835. </button>
  836. </div>
  837. <ReactApexChart
  838. options={options}
  839. series={options.series}
  840. type="line"
  841. height="auto"
  842. />
  843. </div>
  844. </Card>
  845. </Grid>
  846. </div>
  847. <div
  848. style={{
  849. display: "inline-block",
  850. width: "24%",
  851. verticalAlign: "top",
  852. marginLeft: 10,
  853. }}
  854. >
  855. <Grid item xs={12} md={12} lg={12}>
  856. <Card>
  857. <CardHeader
  858. className="text-slate-500"
  859. title= {t("Accounts Receivable (HKD)")}
  860. />
  861. <div style={{ display: "inline-block", width: "99%" }}>
  862. <ReactApexChart
  863. options={accountsReceivableOptions}
  864. series={accountsReceivableOptions.series}
  865. type="radialBar"
  866. />
  867. </div>
  868. <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100">
  869. <div
  870. className="text-sm font-medium ml-5 mt-2"
  871. style={{ color: "#898d8d" }}
  872. >
  873. {t("Total Project Fee")}
  874. </div>
  875. <div
  876. className="text-lg font-medium mx-5"
  877. style={{ color: "#6b87cf", textAlign: "right" }}
  878. >
  879. ${totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  880. </div>
  881. <hr />
  882. <div
  883. className="text-sm font-medium ml-5 mt-2"
  884. style={{ color: "#898d8d" }}
  885. >
  886. {t("Total Invoiced Amount")}
  887. </div>
  888. <div
  889. className="text-lg font-medium mx-5"
  890. style={{ color: "#6b87cf", textAlign: "right" }}
  891. >
  892. ${totalInvoiced.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("Total Received Amount")}
  900. </div>
  901. <div
  902. className="text-lg font-medium mx-5"
  903. style={{ color: "#6b87cf", textAlign: "right" }}
  904. >
  905. ${totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  906. </div>
  907. <hr />
  908. <div
  909. className="text-sm font-medium ml-5"
  910. style={{ color: "#898d8d" }}
  911. >
  912. {t("Accounts Receivable")}
  913. </div>
  914. <div
  915. className="text-lg font-medium mx-5 mb-2"
  916. style={{ color: "#6b87cf", textAlign: "right" }}
  917. >
  918. ${receivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  919. </div>
  920. </Card>
  921. </Card>
  922. </Grid>
  923. </div>
  924. <div
  925. style={{
  926. display: "inline-block",
  927. width: "24%",
  928. verticalAlign: "top",
  929. marginLeft: 10,
  930. }}
  931. >
  932. <Grid item xs={12} md={12} lg={12}>
  933. <Card>
  934. <CardHeader
  935. className="text-slate-500"
  936. title={t("Expenditure (HKD)")}
  937. />
  938. <ReactApexChart
  939. options={expenditureOptions}
  940. series={expenditureOptions.series}
  941. type="radialBar"
  942. />
  943. <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100">
  944. <div
  945. className="text-sm font-medium ml-5 mt-2"
  946. style={{ color: "#898d8d" }}
  947. >
  948. {t("Total Budget")}
  949. </div>
  950. <div
  951. className="text-lg font-medium mx-5"
  952. style={{ color: "#6b87cf", textAlign: "right" }}
  953. >
  954. ${totalBudget.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("Total Cumulative Expenditure")}
  962. </div>
  963. <div
  964. className="text-lg font-medium mx-5"
  965. style={{ color: "#6b87cf", textAlign: "right" }}
  966. >
  967. ${totalExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  968. </div>
  969. <hr />
  970. <div
  971. className="text-sm font-medium ml-5"
  972. style={{ color: "#898d8d" }}
  973. >
  974. {t("Total Manhours Spent")}
  975. </div>
  976. <div
  977. className="text-lg font-medium mx-5"
  978. style={{ color: "#6b87cf", textAlign: "right" }}
  979. >
  980. ${((totalExpenditure || 0 )-(expense || 0)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  981. </div>
  982. <hr />
  983. <div
  984. className="text-sm font-medium ml-5"
  985. style={{ color: "#898d8d" }}
  986. >
  987. {t("Total Project expense")}
  988. </div>
  989. <div
  990. className="text-lg font-medium mx-5"
  991. style={{ color: "#6b87cf", textAlign: "right" }}
  992. >
  993. ${(expense || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  994. </div>
  995. <hr />
  996. <div
  997. className="text-sm font-medium ml-5"
  998. style={{ color: "#898d8d" }}
  999. >
  1000. {t("Remaining Budget")}
  1001. </div>
  1002. <div
  1003. className="text-lg font-medium mx-5 mb-2"
  1004. style={{ color: "#6b87cf", textAlign: "right" }}
  1005. >
  1006. ${expenditureReceivable.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
  1007. </div>
  1008. </Card>
  1009. </Card>
  1010. </Grid>
  1011. </div>
  1012. <div
  1013. className="mt-5"
  1014. style={{
  1015. display: "inline-block",
  1016. width: "100%",
  1017. verticalAlign: "top",
  1018. }}
  1019. >
  1020. <Grid item xs={12} md={12} lg={12}>
  1021. <Card>
  1022. <CardHeader
  1023. className="text-slate-500"
  1024. title= {t("Anticipate Cash Flow by Month")}
  1025. />
  1026. <div style={{ display: "inline-block", width: "99%" }}>
  1027. <div className="inline-block">
  1028. <Label className="text-slate-500 font-medium ml-6">
  1029. {t("Year")}:&nbsp;
  1030. </Label>
  1031. <Input
  1032. id={"cashFlowYear"}
  1033. value={anticipateCashFlowYear}
  1034. readOnly={true}
  1035. bsSize="lg"
  1036. className="rounded-md text-base w-12"
  1037. />
  1038. </div>
  1039. <div className="inline-block ml-1">
  1040. <button
  1041. onClick={() => setAnticipateCashFlowYear(anticipateCashFlowYear - 1)}
  1042. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  1043. >
  1044. &lt;
  1045. </button>
  1046. </div>
  1047. <div className="inline-block ml-1">
  1048. <button
  1049. onClick={() => setAnticipateCashFlowYear(anticipateCashFlowYear + 1)}
  1050. className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base"
  1051. >
  1052. &gt;
  1053. </button>
  1054. </div>
  1055. <ReactApexChart
  1056. options={anticipateOptions}
  1057. series={anticipateOptions.series}
  1058. type="line"
  1059. height="350"
  1060. />
  1061. </div>
  1062. </Card>
  1063. <Card>
  1064. <CardHeader
  1065. className="text-slate-500"
  1066. title= {t("Cash Flow Ledger by Month")}
  1067. />
  1068. <div className="ml-4 mr-4">
  1069. <CustomDatagrid
  1070. rows={ledgerData}
  1071. columns={ledgerColumns}
  1072. columnWidth={200}
  1073. dataGridHeight={300}
  1074. />
  1075. </div>
  1076. </Card>
  1077. </Grid>
  1078. </div>
  1079. </Grid>
  1080. </>
  1081. );
  1082. };
  1083. export default ProjectCashFlow;