diff --git a/src/app/(main)/dashboard/ProjectCashFlow/page.tsx b/src/app/(main)/dashboard/ProjectCashFlow/page.tsx new file mode 100644 index 0000000..e0e1402 --- /dev/null +++ b/src/app/(main)/dashboard/ProjectCashFlow/page.tsx @@ -0,0 +1,31 @@ +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import DashboardPage from "@/components/DashboardPage/DashboardPage"; +import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; +import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; +import { Suspense} from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Typography from "@mui/material/Typography"; +import ProjectCashFlowComponent from '@/components/ProjectCashFlow' + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + + +const ProjectCashFlow: React.FC = () => { + + return ( + + + Project Cash Flow + + {/* }> + + */} + + + ); +}; +export default ProjectCashFlow; diff --git a/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx b/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx new file mode 100644 index 0000000..090ef2d --- /dev/null +++ b/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx @@ -0,0 +1,28 @@ +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import DashboardPage from "@/components/DashboardPage/DashboardPage"; +import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense} from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Typography from "@mui/material/Typography"; +import ProjectFinancialSummaryComponents from "@/components/ProjectFinancialSummary"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + + +const ProjectFinancialSummary: React.FC = () => { + + return ( + + + Project Financial Summary + + + + ); +}; +export default ProjectFinancialSummary; diff --git a/src/app/api/cashflow/index.ts b/src/app/api/cashflow/index.ts new file mode 100644 index 0000000..9b79d52 --- /dev/null +++ b/src/app/api/cashflow/index.ts @@ -0,0 +1,39 @@ +import { cache } from "react"; + +export interface CashFlow { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; +} + +export const preloadProjects = () => { + fetchProjectsCashFlow(); +}; + +export const fetchProjectsCashFlow = cache(async () => { + return mockProjects; +}); + +const mockProjects: CashFlow[] = [ + { + id: 1, + projectCode: "CUST-001", + projectName: "Client A", + team: "N/A", + teamLeader: "N/A", + startDate: "5", + startDateFrom: "5", + startDateTo: "5", + targetEndDate: "s", + client: "ss", + subsidiary:"ss", + } +]; diff --git a/src/components/CustomDatagrid/CustomDatagrid.tsx b/src/components/CustomDatagrid/CustomDatagrid.tsx index d2e7d26..718c2ce 100644 --- a/src/components/CustomDatagrid/CustomDatagrid.tsx +++ b/src/components/CustomDatagrid/CustomDatagrid.tsx @@ -119,7 +119,7 @@ const CustomDatagrid: React.FC = ({ return (
{Title ? ( - + {Title && } {Style ? ( @@ -215,7 +215,7 @@ const CustomDatagrid: React.FC = ({ rows={rowsWithDefaultValues} columns={modifiedColumns} editMode="row" - style={{marginRight:20}} + style={{marginRight:0}} checkboxSelection={checkboxSelection} onRowSelectionModelChange={onRowSelectionModelChange} initialState={{ diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 3e0ce58..8df7a44 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -31,8 +31,9 @@ interface NavigationItem { const navigationItems: NavigationItem[] = [ { icon: , label: "User Workspace", path: "/home" }, { icon: , label: "Dashboard", path: "", children: [ - { icon: , label: "Project Status by Client", path: "/dashboard/ProjectStatusByClient" }, - { icon: , label: "Subitem 2", path: "/dashboard/subitem2" }, + { icon: , label: "Project Financial Summary", path: "/dashboard/ProjectFinancialSummary" }, + { icon: , label: "Project Cash Flow", path: "/dashboard/ProjectCashFlow" }, + { icon: , label: "Project Status by Client", path: "/dashboard/ProjectStatusByClient" }, ]}, { icon: , label: "Expense Claim", path: "/claim" }, { icon: , label: "Project Management", path: "/projects" }, diff --git a/src/components/ProgressCashFlowSearch/ProgressCashFlowSearch.tsx b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearch.tsx new file mode 100644 index 0000000..6787b57 --- /dev/null +++ b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearch.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { ProjectResult } from "@/app/api/projects"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults"; +import { CashFlow } from "@/app/api/cashflow"; +import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ProjectCashFlow from '../ProjectCashFlow' + +interface Props { + projects: CashFlow[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ProgressByClientSearch: React.FC = ({ projects}) => { + const { t } = useTranslation("projects"); + const [selectionModel, setSelectionModel] : any[] = React.useState([]); + const columns = [ + { + id: 'projectCode', + field: 'projectCode', + headerName: "Project Code", + flex: 1, + }, + { + id: 'projectName', + field: 'projectName', + headerName: "Project Name", + flex: 1, + }, + { + id: 'team', + field: 'team', + headerName: "Team", + flex: 1, + }, + { + id: 'teamLeader', + field: 'teamLeader', + headerName: "Team Leader", + flex: 1, + }, + { + id: 'startDate', + field: 'startDate', + headerName: "Start Date", + flex: 1, + }, + { + id: 'targetEndDate', + field: 'targetEndDate', + headerName: "Target End Date", + flex: 1, + }, + { + id: 'client', + field: 'client', + headerName: "Client", + flex: 1, + }, + { + id: 'subsidiary', + field: 'subsidiary', + headerName: "Subsidiary", + flex: 1, + }, +]; + const rows = [{id: 1,projectCode:"M1001",projectName:"Consultancy Project A", team:"XXX", teamLeader:"XXX", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, + {id: 2,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"XXX", teamLeader:"XXX", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, + {id: 3,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} + ] + const [selectedTeamData, setSelectedTeamData] : any[] = React.useState(rows); + const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = selectedTeamData.filter((row:any) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData) + } + + // If project searching is done on the server-side, then no need for this. + const [filteredProjects, setFilteredProjects] = useState(projects); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { label: "Project Code", paramName: "projectCode", type: "text" }, + { label: "Project Name", paramName: "projectName", type: "text" }, + { label: "Start Date From",label2: "Start Date To", paramName: "startDateFrom", type: "dateRange" }, + ], + [t], + ); + + // const columns = useMemo[]>( + // () => [ + // { name: "clientCode", label: t("Project Code") }, + // { name: "clientName", label: t("Project Name") }, + // { name: "SubsidiaryClientCode", label: t("Project Category") }, + // { name: "SubsidiaryClientName", label: t("Team") }, + // { name: "NoOfProjects", label: t("Client") }, + // ], + // [t], + // ); + + return ( + <> + { + console.log(query); + }} + /> + {/* */} + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchLoading.tsx b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchLoading.tsx new file mode 100644 index 0000000..7d25500 --- /dev/null +++ b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchLoading.tsx @@ -0,0 +1,40 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const ProgressCashFlowSearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProgressCashFlowSearchLoading; diff --git a/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchWrapper.tsx b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchWrapper.tsx new file mode 100644 index 0000000..8e99756 --- /dev/null +++ b/src/components/ProgressCashFlowSearch/ProgressCashFlowSearchWrapper.tsx @@ -0,0 +1,18 @@ +import { fetchProjectsCashFlow } from "@/app/api/cashflow"; +import React from "react"; +import ProgressCashFlowSearch from "./ProgressCashFlowSearch"; +import ProgressCashFlowSearchSearchLoading from "./ProgressCashFlowSearchLoading"; + +interface SubComponents { + Loading: typeof ProgressCashFlowSearchSearchLoading; +} + +const ProgressCashFlowSearchWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchProjectsCashFlow(); + + return ; +}; + +ProgressCashFlowSearchWrapper.Loading = ProgressCashFlowSearchSearchLoading; + +export default ProgressCashFlowSearchWrapper; diff --git a/src/components/ProgressCashFlowSearch/index.ts b/src/components/ProgressCashFlowSearch/index.ts new file mode 100644 index 0000000..417711b --- /dev/null +++ b/src/components/ProgressCashFlowSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./ProgressCashFlowSearchWrapper"; diff --git a/src/components/ProjectCashFlow/ProjectCashFlow.tsx b/src/components/ProjectCashFlow/ProjectCashFlow.tsx new file mode 100644 index 0000000..d4965b9 --- /dev/null +++ b/src/components/ProjectCashFlow/ProjectCashFlow.tsx @@ -0,0 +1,210 @@ +"use client"; +import * as React from "react"; +import Grid from "@mui/material/Grid"; +import { useState,useEffect, useMemo } from 'react' +import Paper from "@mui/material/Paper"; +import { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; +import {Card,CardHeader} from '@mui/material'; +import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; +import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ReportProblemIcon from '@mui/icons-material/ReportProblem'; +import dynamic from 'next/dynamic'; +import '../../app/global.css'; +import { AnyARecord, AnyCnameRecord } from "dns"; +import SearchBox, { Criterion } from "../SearchBox"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; +import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; + +const ProjectCashFlow: React.FC = () => { + const [selectionModel, setSelectionModel] : any[] = React.useState([]); +// const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ +// name: 'Monthly Income', +// type: 'line', +// data: [80, 55, 40, 65, 70], +// }, +// { +// name: 'Monthly Incomess', +// type: 'column', +// data: [80, 55, 40, 65, 70], +// } +// ]; + const columns = [ + { + id: 'projectCode', + field: 'projectCode', + headerName: "Project Code", + flex: 1, + }, + { + id: 'projectName', + field: 'projectName', + headerName: "Project Name", + flex: 1, + }, + { + id: 'team', + field: 'team', + headerName: "Team", + flex: 1, + }, + { + id: 'teamLeader', + field: 'teamLeader', + headerName: "Team Leader", + flex: 1, + }, + { + id: 'startDate', + field: 'startDate', + headerName: "Start Date", + flex: 1, + }, + { + id: 'targetEndDate', + field: 'targetEndDate', + headerName: "Target End Date", + flex: 1, + }, + { + id: 'client', + field: 'client', + headerName: "Client", + flex: 1, + }, + { + id: 'subsidiary', + field: 'subsidiary', + headerName: "Subsidiary", + flex: 1, + }, + ]; + + const options: ApexOptions = { + chart: { + height: 350, + type: 'line', + }, + plotOptions: { + bar: { + horizontal: false, + distributed: false, + }, + }, + dataLabels: { + enabled: false + }, + xaxis: { + categories: [ + 'Q1', + 'Q2', + 'Q3', + 'Q4', + 'Q5', + 'Q6', + 'Q7', + 'Q8', + 'Q9', + 'Q10', + 'Q11', + 'Q12', + ], + }, + yaxis: [{ + title: { + text: 'Monthly Income and Expenditure (HKD)' + }, + labels: { + maxWidth: 300, + style: { + cssClass: 'apexcharts-yaxis-label', + }, + }, + }, + { + opposite: true, + title: { + text: 'Cumulative Income and Expenditure (HKD)' + }} + ], + title: { + text: 'Current Stage Completion Percentage', + align: 'center' + }, + grid: { + borderColor: '#f1f1f1', + }, + annotations: { + }, + series:[ + { + name:"Monthly Income", + type:"column", + color: "#ffde91", + data:[0,110000,0,0,185000,0,0,189000,0,0,300000,0] + }, + { + name:"Monthly Expenditure", + type:"column", + color: "#82b59d", + data:[0,160000,120000,120000,55000,55000,55000,55000,55000,70000,55000,55000] + }, + { + name:"Cumulative Income", + type:"line", + color: "#EE6D7A", + data:[1,2,3,5,6,9,8,5,6,1,16,15] + }, + + { + name:"Cumulative Expenditure", + type:"line", + color: "#EE6D7A", + data:[1,2,3,5,6,9,8,5,6,1,16,15] + } + ] + }; + + const rows = [{id: 1,projectCode:"M1001",projectName:"Consultancy Project A", team:"XXX", teamLeader:"XXX", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, + {id: 2,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"XXX", teamLeader:"XXX", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, + {id: 3,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} + ] + const [selectedTeamData, setSelectedTeamData] : any[] = React.useState(rows); + const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = selectedTeamData.filter((row:any) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData) + } + + return ( + <> + }> + + + + +
+ + + +
+ +
+
+
+
+
+ + ); +}; + +export default ProjectCashFlow; diff --git a/src/components/ProjectCashFlow/index.ts b/src/components/ProjectCashFlow/index.ts new file mode 100644 index 0000000..af238d3 --- /dev/null +++ b/src/components/ProjectCashFlow/index.ts @@ -0,0 +1 @@ +export { default } from "./ProjectCashFlow"; diff --git a/src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx b/src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx new file mode 100644 index 0000000..b03e019 --- /dev/null +++ b/src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx @@ -0,0 +1,75 @@ +import * as React from "react"; +import Grid from "@mui/material/Grid"; +import { useState,useEffect, useMemo } from 'react' +import Paper from "@mui/material/Paper"; +import { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; +import {Card,CardHeader} from '@mui/material'; +import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; +import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ReportProblemIcon from '@mui/icons-material/ReportProblem'; +import dynamic from 'next/dynamic'; +import '../../app/global.css'; +import { AnyARecord, AnyCnameRecord } from "dns"; +import SearchBox, { Criterion } from "../SearchBox"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; + +interface Props { + Title: string; + TotalActiveProjectNumber: string; + TotalFees: string; + TotalBudget: string; + TotalCumulative: string; + TotalInvoicedAmount: string; + TotalReceivedAmount: string; + CashFlowStatus: string; + CostPerformanceIndex: string; + ClickedIndex: number; + Index: number; + } + +const ProjectFinancialCard: React.FC = ({Title,TotalActiveProjectNumber,TotalFees,TotalBudget,TotalCumulative,TotalInvoicedAmount,TotalReceivedAmount,CashFlowStatus,CostPerformanceIndex,ClickedIndex,Index}) => { + const [SearchCriteria, setSearchCriteria] = React.useState({}) + const { t } = useTranslation("dashboard"); + const borderColor = CashFlowStatus === "Negative" ? "border-red-300 border-solid" : "border-green-200 border-solid" + const selectedBackgroundColor = ClickedIndex === Index ? "rgb(235 235 235)" : "rgb(255 255 255)" + console.log(ClickedIndex) + console.log(Index) + return ( + +
{Title}

+
Total Active Project
+
{TotalActiveProjectNumber}

+
Total Fees
+
{TotalFees}

+
Total Budget
+
{TotalBudget}

+
Total Cumulative Expenditure
+
{TotalCumulative}

+
Total Invoiced Amount
+
{TotalInvoicedAmount}

+
Total Received Amount
+
{TotalReceivedAmount}

+
Cash Flow Status
+ {CashFlowStatus === "Negative" && ( + <>
{CashFlowStatus}

+ )} + {CashFlowStatus === "Positive" && ( + <>
{CashFlowStatus}

+ )} +
Cost Performance Index (CPI)
+ {Number(CostPerformanceIndex) < 1 && ( + <>
{CostPerformanceIndex}
+ )} + {Number(CostPerformanceIndex) >= 1 && ( + <>
{CostPerformanceIndex}
+ )} +
+ ); + }; + + export default ProjectFinancialCard; \ No newline at end of file diff --git a/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx new file mode 100644 index 0000000..f497e5b --- /dev/null +++ b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx @@ -0,0 +1,272 @@ +"use client"; +import * as React from "react"; +import Grid from "@mui/material/Grid"; +import { useState,useEffect, useMemo } from 'react' +import Paper from "@mui/material/Paper"; +import { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; +import {Card,CardHeader} from '@mui/material'; +import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; +import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ReportProblemIcon from '@mui/icons-material/ReportProblem'; +import dynamic from 'next/dynamic'; +import '../../app/global.css'; +import { AnyARecord, AnyCnameRecord } from "dns"; +import SearchBox, { Criterion } from "../SearchBox"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; +import ProjectFinancialCard from "./ProjectFinancialCard"; + +const ProjectFinancialSummary: React.FC = () => { + const [SearchCriteria, setSearchCriteria] = React.useState({}) + const { t } = useTranslation("dashboard"); + const [selectionModel, setSelectionModel] : any[] = React.useState([]); + const projectFinancialData = [ + {id:1,title:"All Teams",activeProject:"147",fees:"22,800,000.00",budget:"18,240,000.00",cumulativeExpenditure:"17,950,000.00",invoicedAmount:"18,240,000.00",receivedAmount:"10,900,000.00",cashFlowStatus:"Negative",CPI:"0.69"}, + {id:2,title:"XXX Team",activeProject:"25",fees:"1,500,000.00",budget:"1,200,000.00",cumulativeExpenditure:"1,250,000.00",invoicedAmount:"900,000.00",receivedAmount:"650,000.00",cashFlowStatus:"Negative",CPI:"0.72"}, + {id:3,title:"YYY Team",activeProject:"35",fees:"5,000,000.00",budget:"4,000,000.00",cumulativeExpenditure:"3,200,000.00",invoicedAmount:"3,500,000.00",receivedAmount:"3,500,000.00",cashFlowStatus:"Positive",CPI:"1.09"}, + {id:4,title:"ZZZ Team",activeProject:"50",fees:"3,500,000.00",budget:"2,800,000.00",cumulativeExpenditure:"5,600,000.00",invoicedAmount:"2,500,000.00",receivedAmount:"2,200,000.00",cashFlowStatus:"Negative",CPI:"0.45"}, + {id:5,title:"AAA Team",activeProject:"15",fees:"4,800,000.00",budget:"3,840,000.00",cumulativeExpenditure:"2,500,000.00",invoicedAmount:"1,500,000.00",receivedAmount:"750,000.00",cashFlowStatus:"Negative",CPI:"0.60"}, + {id:6,title:"BBB Team",activeProject:"22",fees:"8,000,000.00",budget:"6,400,000.00",cumulativeExpenditure:"5,400,000.00",invoicedAmount:"4,000,000.00",receivedAmount:"3,800,000.00",cashFlowStatus:"Negative",CPI:"0.74"} + ] + + const rows0 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, + {id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"}, + {id: 3,projectCode:"M1001",projectName:"Consultancy Project A", team:"YYY", teamLeader:"YYY", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, + {id: 4,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"YYY", teamLeader:"YYY", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, + {id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} + ] + + const rows1 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, + {id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"}, + ] + + const rows2 = [{id: 3,projectCode:"M1001",projectName:"Consultancy Project A", team:"YYY", teamLeader:"YYY", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, + {id: 4,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"YYY", teamLeader:"YYY", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, + {id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} + ] + + const projectFinancialRows = [{id: 1,cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00", totalUnReceivedAmount:"0.00"} + ] + + const [isCardClickedIndex, setIsCardClickedIndex] = React.useState(0); + + const [selectedTeamData, setSelectedTeamData] : any[] = React.useState(rows0); + + const handleCardClick = (r:any) => { + setIsCardClickedIndex(r) + if (r === 0) { + setSelectedTeamData(rows0) + } else if (r === 1) { + setSelectedTeamData(rows1) + } else if (r === 2) { + setSelectedTeamData(rows2) + } + + }; + + + const columns = [ + { + id: 'projectCode', + field: 'projectCode', + headerName: "Project Code", + flex: 1, + }, + { + id: 'projectName', + field: 'projectName', + headerName: "Project Name", + flex: 1, + }, + { + id: 'team', + field: 'team', + headerName: "Team", + flex: 1, + }, + { + id: 'teamLeader', + field: 'teamLeader', + headerName: "Team Leader", + flex: 1, + }, + { + id: 'startDate', + field: 'startDate', + headerName: "Start Date", + flex: 1, + }, + { + id: 'targetEndDate', + field: 'targetEndDate', + headerName: "Target End Date", + flex: 1, + }, + { + id: 'client', + field: 'client', + headerName: "Client", + flex: 1, + }, + { + id: 'subsidiary', + field: 'subsidiary', + headerName: "Subsidiary", + flex: 1, + }, +]; + +const columns2 = [ + { + id: 'cashFlowStatus', + field: 'cashFlowStatus', + headerName: "Cash Flow Status", + flex: 1, + renderCell: (params:any) => { + if (params.row.cashFlowStatus === "Positive") { + return ( + {params.row.cashFlowStatus} + ) + } else if (params.row.cashFlowStatus === "Negative") { + return ( + {params.row.cashFlowStatus} + ) + } + }, +}, +{ + id: 'cpi', + field: 'cpi', + headerName: "CPI", + flex: 0.7, + renderCell: (params:any) => { + if (params.row.cpi >= 1) { + return ( + {params.row.cpi} + ) + } else if (params.row.cpi < 1) { + return ( + {params.row.cpi} + ) + } + }, +}, +{ + id: 'totalFees', + field: 'totalFees', + headerName: "Total Fees (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalFees} + ) + }, +}, +{ + id: 'totalBudget', + field: 'totalBudget', + headerName: "Total Budget (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalBudget} + ) +}, +}, +{ + id: 'totalCumulativeExpenditure', + field: 'totalCumulativeExpenditure', + headerName: "Total Cumulative Expenditure (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalCumulativeExpenditure} + ) + }, +}, +{ + id: 'totalInvoicedAmount', + field: 'totalInvoicedAmount', + headerName: "Total Invoiced Amount (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalInvoicedAmount} + ) + }, +}, +{ + id: 'totalUnInvoicedAmount', + field: 'totalUnInvoicedAmount', + headerName: "Total Un-invoiced Amount (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalUnInvoicedAmount} + ) + }, +}, +{ + id: 'totalReceivedAmount', + field: 'totalReceivedAmount', + headerName: "Total Received Amount (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalReceivedAmount} + ) + }, +}, +{ + id: 'totalUnReceivedAmount', + field: 'totalUnReceivedAmount', + headerName: "Total Un-received Amount (HKD)", + flex: 1, + renderCell: (params:any) => { + return ( + ${params.row.totalUnReceivedAmount} + ) + }, +}, +]; + + const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = selectedTeamData.filter((row:any) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData) + } + + return ( + + + +
+ {projectFinancialData.map((record, index) => ( +
handleCardClick(index)}> + +
+ ))} +
+
+ + +
+ +
+
+ + +
+ +
+
+
+ ); +}; + +export default ProjectFinancialSummary; diff --git a/src/components/ProjectFinancialSummary/index.ts b/src/components/ProjectFinancialSummary/index.ts new file mode 100644 index 0000000..724bb4a --- /dev/null +++ b/src/components/ProjectFinancialSummary/index.ts @@ -0,0 +1 @@ +export { default } from "./ProjectFinancialSummary"; diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 790d16a..7606037 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -15,10 +15,16 @@ import CardActions from "@mui/material/CardActions"; import Button from "@mui/material/Button"; import RestartAlt from "@mui/icons-material/RestartAlt"; import Search from "@mui/icons-material/Search"; +import dayjs from 'dayjs'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; interface BaseCriterion { label: string; + label2?: string; paramName: T; + paramName2?: T; } interface TextCriterion extends BaseCriterion { @@ -30,7 +36,11 @@ interface SelectCriterion extends BaseCriterion { options: string[]; } -export type Criterion = TextCriterion | SelectCriterion; +interface DateRangeCriterion extends BaseCriterion { + type: "dateRange"; +} + +export type Criterion = TextCriterion | SelectCriterion | DateRangeCriterion; interface Props { criteria: Criterion[]; @@ -44,6 +54,7 @@ function SearchBox({ onReset, }: Props) { const { t } = useTranslation("common"); + const [dayRangeFromDate, setDayRangeFromDate] :any = useState(""); const defaultInputs = useMemo( () => criteria.reduce>( @@ -71,6 +82,24 @@ function SearchBox({ }; }, []); + const makeDateChangeHandler = useCallback( + (paramName: T) => { + return (e:any) => { + setInputs((i) => ({ ...i, [paramName]: dayjs(e).format('YYYY-MM-DD') })); + }; + }, + [], + ); + + const makeDateToChangeHandler = useCallback( + (paramName: T) => { + return (e:any) => { + setInputs((i) => ({ ...i, [paramName + "To"]: dayjs(e).format('YYYY-MM-DD') })); + }; + }, + [], + ); + const handleReset = () => { setInputs(defaultInputs); onReset?.(); @@ -113,6 +142,35 @@ function SearchBox({ )} + {c.type === "dateRange" && ( + + + + + + + + + + + - + + + + + + + + + + + )} ); })}