From 1f8a4e18c933004d12dd6a62f0e7e2de3d472187 Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Mon, 29 Apr 2024 16:48:28 +0800 Subject: [PATCH] add project resource summary page --- .../dashboard/ProjectResourceSummary/page.tsx | 29 + .../dashboard/StaffUtilization/page.tsx | 2 +- src/app/api/clientprojects/index.ts | 6 +- src/app/api/resourcesummary/index.ts | 53 ++ .../CustomDatagrid/CustomDatagrid.tsx | 18 +- .../NavigationContent/NavigationContent.tsx | 6 + .../ProgressByClient/ProgressByClient.tsx | 3 +- .../ProgressByClientSearch.tsx | 21 +- .../ProgressByTeam/ProgressByTeam.tsx | 3 +- .../ProjectResourceSummary.tsx | 548 ++++++++++++++++++ .../ProjectResourceSummary/index.ts | 1 + .../ProjectResourceSummarySearch.tsx | 75 +++ .../ProjectResourceSummarySearchLoading.tsx | 40 ++ .../ProjectResourceSummarySearchWrapper.tsx | 20 + .../ProjectResourceSummarySearch/index.ts | 1 + 15 files changed, 814 insertions(+), 12 deletions(-) create mode 100644 src/app/(main)/dashboard/ProjectResourceSummary/page.tsx create mode 100644 src/app/api/resourcesummary/index.ts create mode 100644 src/components/ProjectResourceSummary/ProjectResourceSummary.tsx create mode 100644 src/components/ProjectResourceSummary/index.ts create mode 100644 src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx create mode 100644 src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.tsx create mode 100644 src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx create mode 100644 src/components/ProjectResourceSummarySearch/index.ts diff --git a/src/app/(main)/dashboard/ProjectResourceSummary/page.tsx b/src/app/(main)/dashboard/ProjectResourceSummary/page.tsx new file mode 100644 index 0000000..5dc2f77 --- /dev/null +++ b/src/app/(main)/dashboard/ProjectResourceSummary/page.tsx @@ -0,0 +1,29 @@ +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import DashboardPage from "@/components/DashboardPage/DashboardPage"; +import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; +import { Suspense } from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Typography from "@mui/material/Typography"; +import StaffUtilizationComponent from "@/components/StaffUtilization"; +import ProjectResourceSummarySearch from "@/components/ProjectResourceSummarySearch"; +import { ResourceSummaryResult } from "@/app/api/resourcesummary"; + +export const metadata: Metadata = { + title: "Project Resource Summary", +}; + +const ProjectResourceSummary: React.FC = () => { + return ( + + + Project Resource Summary + + }> + + + + ); +}; +export default ProjectResourceSummary; diff --git a/src/app/(main)/dashboard/StaffUtilization/page.tsx b/src/app/(main)/dashboard/StaffUtilization/page.tsx index 2ddea02..87bb6c0 100644 --- a/src/app/(main)/dashboard/StaffUtilization/page.tsx +++ b/src/app/(main)/dashboard/StaffUtilization/page.tsx @@ -10,7 +10,7 @@ import Typography from "@mui/material/Typography"; import StaffUtilizationComponent from "@/components/StaffUtilization"; export const metadata: Metadata = { - title: "Project Status by Client", + title: "Staff Utilization", }; const StaffUtilization: React.FC = () => { diff --git a/src/app/api/clientprojects/index.ts b/src/app/api/clientprojects/index.ts index 5c65810..3eed422 100644 --- a/src/app/api/clientprojects/index.ts +++ b/src/app/api/clientprojects/index.ts @@ -27,7 +27,7 @@ const mockProjects: ClientProjectResult[] = [ NoOfProjects: 5, }, { - id: 1, + id: 2, clientCode: "CUST-001", clientName: "Client A", SubsidiaryClientCode: "SUBS-001", @@ -35,7 +35,7 @@ const mockProjects: ClientProjectResult[] = [ NoOfProjects: 5, }, { - id: 1, + id: 3, clientCode: "CUST-001", clientName: "Client A", SubsidiaryClientCode: "SUBS-002", @@ -43,7 +43,7 @@ const mockProjects: ClientProjectResult[] = [ NoOfProjects: 3, }, { - id: 1, + id: 4, clientCode: "CUST-001", clientName: "Client A", SubsidiaryClientCode: "SUBS-003", diff --git a/src/app/api/resourcesummary/index.ts b/src/app/api/resourcesummary/index.ts new file mode 100644 index 0000000..ffaba69 --- /dev/null +++ b/src/app/api/resourcesummary/index.ts @@ -0,0 +1,53 @@ +import { cache } from "react"; + +export interface ResourceSummaryResult { + id: number; + projectCode: string; + projectName: string; + clientCode: string; + clientName: string; + clientCodeAndName: string; +} + +export const preloadProjects = () => { + fetchResourceSummary(); +}; + +export const fetchResourceSummary = cache(async () => { + return mockProjects; +}); + +const mockProjects: ResourceSummaryResult[] = [ + { + id: 1, + projectCode: 'C-1001-001', + projectName: 'Consultancy Project A', + clientCode: 'Client-001', + clientName: 'AAA Construction', + clientCodeAndName: 'Client-001 - AAA Construction', + }, + { + id: 2, + projectCode: 'C-1002-001', + projectName: 'Consultancy Project B', + clientCode: 'Client-001', + clientName: 'AAA Construction', + clientCodeAndName: 'Client-001 - AAA Construction', + }, + { + id: 3, + projectCode: 'C-1003-001', + projectName: 'Consultancy Project C', + clientCode: 'Client-002', + clientName: 'BBB Construction', + clientCodeAndName: 'Client-002 - BBB Construction', + }, + { + id: 4, + projectCode: 'C-1004-001', + projectName: 'Consultancy Project D', + clientCode: 'Client-002', + clientName: 'BBB Construction', + clientCodeAndName: 'Client-002 - BBB Construction', + }, +]; diff --git a/src/components/CustomDatagrid/CustomDatagrid.tsx b/src/components/CustomDatagrid/CustomDatagrid.tsx index 314ba6c..4867623 100644 --- a/src/components/CustomDatagrid/CustomDatagrid.tsx +++ b/src/components/CustomDatagrid/CustomDatagrid.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; import { Card, CardHeader, CardContent, SxProps, Theme } from "@mui/material"; -import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; +import { DataGrid, GridColDef, GridRowSelectionModel, GridColumnGroupingModel} from "@mui/x-data-grid"; import { darken, lighten, styled } from "@mui/material/styles"; import { useState } from "react"; @@ -19,6 +19,8 @@ interface CustomDatagridProps { newSelectionModel: GridRowSelectionModel, ) => void; selectionModel?: any; + columnGroupingModel?: any; + pageSize?:any; } const CustomDatagrid: React.FC = ({ @@ -32,6 +34,8 @@ const CustomDatagrid: React.FC = ({ checkboxSelection, // Destructure the new prop onRowSelectionModelChange, // Destructure the new prop selectionModel, + columnGroupingModel, + pageSize, ...props }) => { const modifiedColumns = columns.map((column) => { @@ -193,6 +197,8 @@ const CustomDatagrid: React.FC = ({ editMode="row" checkboxSelection={checkboxSelection} onRowSelectionModelChange={onRowSelectionModelChange} + experimentalFeatures={{ columnGrouping: true }} + columnGroupingModel={columnGroupingModel} initialState={{ pagination: { paginationModel: { pageSize: 10 } }, }} @@ -222,6 +228,8 @@ const CustomDatagrid: React.FC = ({ editMode="row" checkboxSelection={checkboxSelection} onRowSelectionModelChange={onRowSelectionModelChange} + experimentalFeatures={{ columnGrouping: true }} + columnGroupingModel={columnGroupingModel} initialState={{ pagination: { paginationModel: { pageSize: 10 } }, }} @@ -251,6 +259,8 @@ const CustomDatagrid: React.FC = ({ editMode="row" checkboxSelection={checkboxSelection} onRowSelectionModelChange={onRowSelectionModelChange} + experimentalFeatures={{ columnGrouping: true }} + columnGroupingModel={columnGroupingModel} style={{ marginRight: 20 }} initialState={{ pagination: { paginationModel: { pageSize: 10 } }, @@ -282,8 +292,10 @@ const CustomDatagrid: React.FC = ({ style={{ marginRight: 0 }} checkboxSelection={checkboxSelection} onRowSelectionModelChange={onRowSelectionModelChange} + experimentalFeatures={{ columnGrouping: true }} + columnGroupingModel={columnGroupingModel} initialState={{ - pagination: { paginationModel: { pageSize: 10 } }, + pagination: { paginationModel: { pageSize: pageSize ?? 10 } }, }} className="customDataGrid" sx={{ @@ -293,7 +305,7 @@ const CustomDatagrid: React.FC = ({ "& .MuiDataGrid-cell:hover": { color: "primary.main", }, - height: 300, + height: dataGridHeight ?? 300, "& .MuiDataGrid-root": { overflow: "auto", }, diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index aef7c0d..1c1f12d 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -31,6 +31,7 @@ import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; import Logo from "../Logo"; import GroupIcon from '@mui/icons-material/Group'; import BusinessIcon from '@mui/icons-material/Business'; +import ViewWeekIcon from '@mui/icons-material/ViewWeek'; import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; @@ -78,6 +79,11 @@ const navigationItems: NavigationItem[] = [ label: "Staff Utilization", path: "/dashboard/StaffUtilization", }, + { + icon: , + label: "Project Resource Summary", + path: "/dashboard/ProjectResourceSummary", + } ], }, { diff --git a/src/components/ProgressByClient/ProgressByClient.tsx b/src/components/ProgressByClient/ProgressByClient.tsx index 6815f73..6750392 100644 --- a/src/components/ProgressByClient/ProgressByClient.tsx +++ b/src/components/ProgressByClient/ProgressByClient.tsx @@ -8,7 +8,7 @@ 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 ReactApexChart from "react-apexcharts"; import { ApexOptions } from "apexcharts"; import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import ReportProblemIcon from "@mui/icons-material/ReportProblem"; @@ -18,6 +18,7 @@ import { AnyARecord, AnyCnameRecord } from "dns"; import SearchBox, { Criterion } from "../SearchBox"; import ProgressByClientSearch from "@/components/ProgressByClientSearch"; import { Suspense } from "react"; +const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); const ProgressByClient: React.FC = () => { const [activeTab, setActiveTab] = useState("financialSummary"); diff --git a/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx index 553a306..2062919 100644 --- a/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx +++ b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx @@ -1,11 +1,13 @@ "use client"; import { ProjectResult } from "@/app/api/projects"; -import React, { useMemo, useState } from "react"; +import React, { useMemo, useState, useCallback } from "react"; import SearchBox, { Criterion } from "../SearchBox"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import { ClientProjectResult } from "@/app/api/clientprojects"; +import EditNote from "@mui/icons-material/EditNote"; +import { useRouter, useSearchParams } from "next/navigation"; interface Props { projects: ClientProjectResult[]; @@ -15,7 +17,7 @@ type SearchParamNames = keyof SearchQuery; const ProgressByClientSearch: React.FC = ({ projects }) => { const { t } = useTranslation("projects"); - + const searchParams = useSearchParams() // If project searching is done on the server-side, then no need for this. const [filteredProjects, setFilteredProjects] = useState(projects); @@ -27,15 +29,28 @@ const ProgressByClientSearch: React.FC = ({ projects }) => { [t], ); + const onTaskClick = useCallback((clientProjectResult: ClientProjectResult) => { + const params = new URLSearchParams(searchParams.toString()) + params.set("id", clientProjectResult.id.toString()) + console.log(clientProjectResult) +}, []); + const columns = useMemo[]>( () => [ + { + name: "id", + label: t("Details"), + onClick: onTaskClick, + buttonIcon: , + }, { name: "clientCode", label: t("Client Code") }, { name: "clientName", label: t("Client Name") }, { name: "SubsidiaryClientCode", label: t("Subsidiary Code") }, { name: "SubsidiaryClientName", label: t("Subisdiary") }, { name: "NoOfProjects", label: t("No. of Projects") }, ], - [t], + [onTaskClick, t], + // [t], ); return ( diff --git a/src/components/ProgressByTeam/ProgressByTeam.tsx b/src/components/ProgressByTeam/ProgressByTeam.tsx index bee6f73..164a00d 100644 --- a/src/components/ProgressByTeam/ProgressByTeam.tsx +++ b/src/components/ProgressByTeam/ProgressByTeam.tsx @@ -8,7 +8,7 @@ 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 ReactApexChart from "react-apexcharts"; import { ApexOptions } from "apexcharts"; import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import ReportProblemIcon from "@mui/icons-material/ReportProblem"; @@ -18,6 +18,7 @@ import { AnyARecord, AnyCnameRecord } from "dns"; import SearchBox, { Criterion } from "../SearchBox"; import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; import { Suspense } from "react"; +const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); const ProgressByTeam: React.FC = () => { const [activeTab, setActiveTab] = useState("financialSummary"); diff --git a/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx b/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx new file mode 100644 index 0000000..57d25d2 --- /dev/null +++ b/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx @@ -0,0 +1,548 @@ +"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 { DataGrid, 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 { getPossibleInstrumentationHookFilenames } from "next/dist/build/utils"; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Collapse from '@mui/material/Collapse'; +import IconButton from '@mui/material/IconButton'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; + + +const ProjectResourceSummary: React.FC = () => { + const [SearchCriteria, setSearchCriteria] = React.useState({}); + const { t } = useTranslation("dashboard"); + const [selectionModel, setSelectionModel]: any[] = React.useState([]); + const [projectName, setProjectName]:any = React.useState("NA"); + const [projectFee, setProjectFee]:any = React.useState(0); + const [status, setStatus]:any = React.useState("NA"); + const [plannedResources, setPlannedResources]:any = React.useState(0); + const [actualResourcesSpent, setActualResourcesSpent]:any = React.useState(0); + const [remainingResources, setRemainingResources]:any = React.useState(0); + + function createData(stage:any, taskCount:any, g1Planned:any, g1Actual:any, g2Planned:any, g2Actual:any, g3Planned:any, g3Actual:any, g4Planned:any, g4Actual:any, g5Planned:any, g5Actual:any, totalPlanned:any, totalActual:any, task:any) { + return { + stage, + taskCount, + g1Planned, + g1Actual, + g2Planned, + g2Actual, + g3Planned, + g3Actual, + g4Planned, + g4Actual, + g5Planned, + g5Actual, + totalPlanned, + totalActual, + task:task + } + } + + function createTaskData(stage:any, taskCount:any, g1Planned:any, g1Actual:any, g2Planned:any, g2Actual:any, g3Planned:any, g3Actual:any, g4Planned:any, g4Actual:any, g5Planned:any, g5Actual:any, totalPlanned:any, totalActual:any) { + return { + stage, + taskCount, + g1Planned, + g1Actual, + g2Planned, + g2Actual, + g3Planned, + g3Actual, + g4Planned, + g4Actual, + g5Planned, + g5Actual, + totalPlanned, + totalActual, + } + } + + const task1Rows:any = [ + {stage:"1.1 Preparation of preliminary...",taskCount:"-",g1Planned:"-",g1Actual:"172.00",g2Planned:"-", g2Actual:"54.00", g3Planned:"-", g3Actual:"42.00", g4Planned: "-", g4Actual:"12.00", g5Planned:"-", g5Actual:"3.00", totalPlanned:"-", totalActual:"283.00"}, + {stage:"1.2 Cash flow forecast",taskCount:"-",g1Planned:"-",g1Actual:"172.00",g2Planned:"-", g2Actual:"54.00", g3Planned:"-", g3Actual:"42.00", g4Planned: "-", g4Actual:"12.00", g5Planned:"-", g5Actual:"3.00", totalPlanned:"-", totalActual:"283.00"}, + {stage:"1.3 Cost studies for alterative design solutions",taskCount:"-",g1Planned:"-",g1Actual:"115.00",g2Planned:"-", g2Actual:"36.00", g3Planned:"-", g3Actual:"28.00", g4Planned: "-", g4Actual:"7.00", g5Planned:"-", g5Actual:"1.75", totalPlanned:"-", totalActual:"188.00"}, + {stage:"1.4 Attend design co-ordiantion / project",taskCount:"-",g1Planned:"-",g1Actual:"29.00",g2Planned:"-", g2Actual:"9.00", g3Planned:"-", g3Actual:"7.00", g4Planned: "-", g4Actual:"2.00", g5Planned:"-", g5Actual:"1.00", totalPlanned:"-", totalActual:"48.00"}, + {stage:"1.5 Prepare / Review RIC",taskCount:"-",g1Planned:"-",g1Actual:"88.00",g2Planned:"-", g2Actual:"27.00", g3Planned:"-", g3Actual:"21.00", g4Planned: "-", g4Actual:"5.00", g5Planned:"-", g5Actual:"1.00", totalPlanned:"-", totalActual:"141.75"} + ] + + const task2Rows:any = [ + ] + + const task3Rows:any = [ + ] + + const task4Rows:any = [ + ] + + const task5Rows:any = [ + ] + + const task6Rows:any = [ + ] + + const rows = [ + createData("Stage 1 - Design & Cost Planning / Estimating","5","576.00","576.00","192.00", "180.00", "144.00", "140.00", "38.40", "38.00", "9.60", "9.75", "960.00", "943.75",task1Rows), + createData("Stage 2 - Tender Documentation","11", "384.00", "382.00", "128.00", "130.00", "96.00", "79.00", "25.60", "25.00", "6.40", "4.00", "640.00", "620.00",task2Rows), + createData("Stage 3 - Tender Analysis & Report & Contract Documentation","7", "384.00", "300.00", "128.00", "130.00", "96.00", "79.00", "25.60", "25.00", "6.40", "4.00", "640.00", "538.00",task3Rows), + createData("Stage 4 - Construction", "13", "480.00", "400.00", "160.00", "160.00", "120.00", "128.00", "32.00", "25.00", "8.00", "3.00", "800.00", "716.00",task4Rows), + createData("Stage 5 - Miscellaneous", "4", "96.00", "-", "32.00", "-", "24.00", "-0", "6.40", "-", "1.600", "-", "160.00", "-",task5Rows), + createData("","Total", "1920.00", "1658.00", "640.00", "600.00", "480.00", "426.00", "128.00", "113.00", "32.00", "20.75", "3,200.00", "2817.75",task6Rows), + ]; + + // const taskRows = [ + // createTaskData("1.1 Preparation of preliminary...","-","-","172.00","-","54.00","-","42.00","-","12.00","-","3.00","-","283.00"), + // ]; + + function Row(props:any) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + {row.task.length > 0 && ( + setOpen(!open)} + > + {open ? : } + + )} + + {row.stage} + {row.taskCount} + {row.g1Planned} + {row.g1Actual} + {row.g2Planned} + {row.g2Actual} + {row.g3Planned} + {row.g3Actual} + {row.g4Planned} + {row.g4Actual} + {row.g5Planned} + {row.g5Actual} + {row.totalPlanned} + {row.totalActual} + + {row.task.map((taskRow:any) => ( + <> + + + + + + + + + {taskRow.stage} + + + + + {taskRow.taskCount} + + + + + {taskRow.g1Planned} + + + + + {taskRow.g1Actual} + + + + + {taskRow.g2Planned} + + + + + {taskRow.g2Actual} + + + + + {taskRow.g3Planned} + + + + + {taskRow.g3Actual} + + + + + {taskRow.g4Planned} + + + + + {taskRow.g4Actual} + + + + + {taskRow.g5Planned} + + + + + {taskRow.g5Actual} + + + + + {taskRow.totalPlanned} + + + + + {taskRow.totalActual} + + + + + ))} + {/* + + + + {row.task.map((taskRow:any) => ( + + + {taskRow.stage} + {taskRow.taskCount} + {taskRow.g1Planned} + {taskRow.g1Actual} + {taskRow.g2Planned} + {taskRow.g2Actual} + {taskRow.g3Planned} + {taskRow.g3Actual} + {taskRow.g4Planned} + {taskRow.g4Actual} + {taskRow.g5Planned} + {taskRow.g5Actual} + {taskRow.totalPlanned} + {taskRow.totalActual} + + ))} + + + + */} + + ); + } + + useEffect(() => { + setProjectName("C-1001-001 - Consultancy Project A") + const fee = 2000000 + setProjectFee(fee.toLocaleString()) + setStatus("Within Budget / Overconsumption") + const plannedResourcesInt = 3200 + setPlannedResources(plannedResourcesInt.toLocaleString()) + const actualResourcesSpentInt = 2817.75 + setActualResourcesSpent(actualResourcesSpentInt.toLocaleString()) + const remainingResourcesInt = 382.25 + setRemainingResources(remainingResourcesInt.toLocaleString()) + }, []) + + const projectResourcesRows = [ + {id: 1,stage:"Stage 1 - Design & Cost Planning / Estimating",taskCount:"5",g1Planned:"576.00",g1Actual:"576.00",g2Planned:"192.00", g2Actual:"180.00", g3Planned:"144.00", g3Actual:"140.00", g4Planned: "38.40", g4Actual:"38S.00", g5Planned:"9.60", g5Actual:"9.75", totalPlanned:"960.00", totalActual:"943.75"}, + {id: 2,stage:"1.1 Preparation of preliminary...",taskCount:"-",g1Planned:"-",g1Actual:"172.00",g2Planned:"-", g2Actual:"54.00", g3Planned:"-", g3Actual:"42.00", g4Planned: "-", g4Actual:"12.00", g5Planned:"-", g5Actual:"3.00", totalPlanned:"-", totalActual:"283.00"}, + {id: 3,stage:"1.2 Cash flow forecast",taskCount:"-",g1Planned:"-",g1Actual:"172.00",g2Planned:"-", g2Actual:"54.00", g3Planned:"-", g3Actual:"42.00", g4Planned: "-", g4Actual:"12.00", g5Planned:"-", g5Actual:"3.00", totalPlanned:"-", totalActual:"283.00"}, + {id: 4,stage:"1.3 Cost studies for alterative design solutions",taskCount:"-",g1Planned:"-",g1Actual:"115.00",g2Planned:"-", g2Actual:"36.00", g3Planned:"-", g3Actual:"28.00", g4Planned: "-", g4Actual:"7.00", g5Planned:"-", g5Actual:"1.75", totalPlanned:"-", totalActual:"188.00"}, + {id: 5,stage:"1.4 Attend design co-ordiantion / project",taskCount:"-",g1Planned:"-",g1Actual:"29.00",g2Planned:"-", g2Actual:"9.00", g3Planned:"-", g3Actual:"7.00", g4Planned: "-", g4Actual:"2.00", g5Planned:"-", g5Actual:"1.00", totalPlanned:"-", totalActual:"48.00"}, + {id: 6,stage:"1.5 Prepare / Review RIC",taskCount:"-",g1Planned:"-",g1Actual:"88.00",g2Planned:"-", g2Actual:"27.00", g3Planned:"-", g3Actual:"21.00", g4Planned: "-", g4Actual:"5.00", g5Planned:"-", g5Actual:"1.00", totalPlanned:"-", totalActual:"141.75"}, + {id: 7,stage:"Stage 2 - Tender Documentation",taskCount:"11",g1Planned:"384.00",g1Actual:"382.00",g2Planned:"128.00", g2Actual:"130.00", g3Planned:"96.00", g3Actual:"79.00", g4Planned: "25.60", g4Actual:"25.00", g5Planned:"6.40", g5Actual:"4.00", totalPlanned:"640.00", totalActual:"620.00"}, + {id: 8,stage:"Stage 3 - Tender Analysis & Report & Contract Documentation",taskCount:"7",g1Planned:"384.00",g1Actual:"300.00",g2Planned:"128.00", g2Actual:"130.00", g3Planned:"96.00", g3Actual:"79.00", g4Planned: "25.60", g4Actual:"25.00", g5Planned:"6.40", g5Actual:"4.00", totalPlanned:"640.00", totalActual:"538.00"}, + {id: 9,stage:"Stage 4 - Construction",taskCount:"13",g1Planned:"480.00",g1Actual:"400.00",g2Planned:"160.00", g2Actual:"160.00", g3Planned:"120.00", g3Actual:"128.00", g4Planned: "32.00", g4Actual:"25.00", g5Planned:"8.00", g5Actual:"3.00", totalPlanned:"800.00", totalActual:"716.00"}, + {id: 10,stage:"Stage 5 - Miscellaneous",taskCount:"4",g1Planned:"96.00",g1Actual:"-",g2Planned:"32.00", g2Actual:"-", g3Planned:"24.00", g3Actual:"-0", g4Planned: "6.40", g4Actual:"-", g5Planned:"1.600", g5Actual:"-", totalPlanned:"160.00", totalActual:"-"}, + {id: 11,stage:"",taskCount:"Total",g1Planned:"1920.00",g1Actual:"1658.00",g2Planned:"640.00", g2Actual:"600.00", g3Planned:"480.00", g3Actual:"426.00", g4Planned: "128.00", g4Actual:"113.00", g5Planned:"32.00", g5Actual:"20.75", totalPlanned:"3,200.00", totalActual:"2817.75"}, + ] + +const columns2 = [ + { + id: 'stage', + field: 'stage', + headerName: "Stage", + flex: 2, + }, + { + id: 'taskCount', + field: 'taskCount', + headerName: "Task Count", + flex: 0.5, + }, + { + id: 'g1Planned', + field: 'g1Planned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'g1Actual', + field: 'g1Actual', + headerName: "Actual", + flex: 0.7, + }, + { + id: 'g2Planned', + field: 'g2Planned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'g2Actual', + field: 'g2Actual', + headerName: "Actual", + flex: 0.7, + }, + { + id: 'g3Planned', + field: 'g3Planned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'g3Actual', + field: 'g3Actual', + headerName: "Actual", + flex: 0.7, + }, + { + id: 'g4Planned', + field: 'g4Planned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'g4Actual', + field: 'g4Actual', + headerName: "Actual", + flex: 0.7, + }, + { + id: 'g5Planned', + field: 'g5Planned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'g5Actual', + field: 'g5Actual', + headerName: "Actual", + flex: 0.7, + }, + { + id: 'totalPlanned', + field: 'totalPlanned', + headerName: "Planned", + flex: 0.7, + }, + { + id: 'totalActual', + field: 'totalActual', + headerName: "Actual", + flex: 0.7, + }, +]; + + const columnGroupingModel = [ + { + groupId: 'G1', + children: [{ field: 'g1Planned' },{ field: 'g1Actual' }], + headerClassName: 'groupColor', + }, + { + groupId: 'G2', + children: [{ field: 'g2Planned' },{ field: 'g2Actual' }], + headerClassName: 'groupColor', + }, + { + groupId: 'G3', + children: [{ field: 'g3Planned' },{ field: 'g3Actual' }], + headerClassName: 'groupColor', + }, + { + groupId: 'G4', + children: [{ field: 'g4Planned' },{ field: 'g4Actual' }], + headerClassName: 'groupColor', + }, + { + groupId: 'G5', + children: [{ field: 'g5Planned' },{ field: 'g5Actual' }], + headerClassName: 'groupColor', + }, + { + groupId: 'Total', + children: [{ field: 'totalPlanned' },{ field: 'totalActual' }], + headerClassName: 'totalGroupColor', + }, + ]; + + return ( + + + +
+
+
+ + Project + +
+
+ {projectName} +
+
+
+
+ + Project Fee + +
+
+ HKD {projectFee} +
+
+
+
+ + Status + +
+
+ {status} +
+
+
+
+ + Planned Resources + +
+
+ {plannedResources} Manhours +
+
+
+
+ + Actual Resources Spent + +
+
+ {actualResourcesSpent} Manhours +
+
+
+
+ + Remaining Resources + +
+
+ {remainingResources} Manhours +
+
+
+ {/*
+ +
*/} +
+ + + + + + + + + G1 + + + G2 + + + G3 + + + G4 + + + G5 + + + Total + + + + + Stage + Task Count + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual + + + + {rows.map((row) => ( + + ))} + +
+
+
+
+
+ ); +}; + +export default ProjectResourceSummary; diff --git a/src/components/ProjectResourceSummary/index.ts b/src/components/ProjectResourceSummary/index.ts new file mode 100644 index 0000000..056f6f9 --- /dev/null +++ b/src/components/ProjectResourceSummary/index.ts @@ -0,0 +1 @@ +export { default } from "./ProjectResourceSummary"; diff --git a/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx new file mode 100644 index 0000000..d8efb61 --- /dev/null +++ b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { ProjectResult } from "@/app/api/projects"; +import React, { useMemo, useState, useCallback } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults"; +import { ResourceSummaryResult } from "@/app/api/resourcesummary"; +import EditNote from "@mui/icons-material/EditNote"; +import { useRouter, useSearchParams } from "next/navigation"; +import ProjectResourceSummary from "@/components/ProjectResourceSummary"; +import ArticleIcon from '@mui/icons-material/Article'; + +interface Props { + projects: ResourceSummaryResult[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + + +const ProjectResourceSummarySearch: React.FC = ({ projects }) => { + const { t } = useTranslation("projects"); + const searchParams = useSearchParams() + // 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: "Client Code", paramName: "clientCode", type: "text" }, + { label: "Client Name", paramName: "clientName", type: "text" }, + ], + [t], + ); + + const onTaskClick = useCallback((resourceSummaryResult: ResourceSummaryResult) => { + console.log(resourceSummaryResult) + }, []); + + + const columns = useMemo[]>( + () => [ + { + name: "id", + label: t("View"), + onClick: onTaskClick, + buttonIcon: , + }, + { name: "projectCode", label: t("Project Code") }, + { name: "projectName", label: t("Project Name") }, + { name: "clientCodeAndName", label: t("Client Code And Name") }, + ], + [onTaskClick, t], + // [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + + items={filteredProjects} + columns={columns} + /> + + + ); +}; + +export default ProjectResourceSummarySearch; diff --git a/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.tsx b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.tsx new file mode 100644 index 0000000..b6d4bc1 --- /dev/null +++ b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.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 ProjectResourceSummarySearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProjectResourceSummarySearchLoading; diff --git a/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx new file mode 100644 index 0000000..068debc --- /dev/null +++ b/src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx @@ -0,0 +1,20 @@ +import { fetchResourceSummary } from "@/app/api/resourcesummary"; +import React from "react"; +import ProjectResourceSummarySearch from "./ProjectResourceSummarySearch"; +import ProjectResourceSummarySearchLoading from "./ProjectResourceSummarySearchLoading"; + +interface SubComponents { + Loading: typeof ProjectResourceSummarySearchLoading; +} + +const ProjectResourceSummarySearchWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchResourceSummary(); + + return ; +}; + +ProjectResourceSummarySearchWrapper.Loading = ProjectResourceSummarySearchLoading; + +export default ProjectResourceSummarySearchWrapper; + + diff --git a/src/components/ProjectResourceSummarySearch/index.ts b/src/components/ProjectResourceSummarySearch/index.ts new file mode 100644 index 0000000..98ec034 --- /dev/null +++ b/src/components/ProjectResourceSummarySearch/index.ts @@ -0,0 +1 @@ +export { default } from "./ProjectResourceSummarySearchWrapper";