Ver a proveniência

add project resource summary page

tags/Baseline_30082024_FRONTEND_UAT
Mac\David há 1 ano
ascendente
cometimento
1f8a4e18c9
15 ficheiros alterados com 814 adições e 12 eliminações
  1. +29
    -0
      src/app/(main)/dashboard/ProjectResourceSummary/page.tsx
  2. +1
    -1
      src/app/(main)/dashboard/StaffUtilization/page.tsx
  3. +3
    -3
      src/app/api/clientprojects/index.ts
  4. +53
    -0
      src/app/api/resourcesummary/index.ts
  5. +15
    -3
      src/components/CustomDatagrid/CustomDatagrid.tsx
  6. +6
    -0
      src/components/NavigationContent/NavigationContent.tsx
  7. +2
    -1
      src/components/ProgressByClient/ProgressByClient.tsx
  8. +18
    -3
      src/components/ProgressByClientSearch/ProgressByClientSearch.tsx
  9. +2
    -1
      src/components/ProgressByTeam/ProgressByTeam.tsx
  10. +548
    -0
      src/components/ProjectResourceSummary/ProjectResourceSummary.tsx
  11. +1
    -0
      src/components/ProjectResourceSummary/index.ts
  12. +75
    -0
      src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx
  13. +40
    -0
      src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.tsx
  14. +20
    -0
      src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx
  15. +1
    -0
      src/components/ProjectResourceSummarySearch/index.ts

+ 29
- 0
src/app/(main)/dashboard/ProjectResourceSummary/page.tsx Ver ficheiro

@@ -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 (
<I18nProvider namespaces={["dashboard"]}>
<Typography variant="h4" marginInlineEnd={2}>
Project Resource Summary
</Typography>
<Suspense fallback={<ProjectResourceSummarySearch.Loading />}>
<ProjectResourceSummarySearch/>
</Suspense>
</I18nProvider>
);
};
export default ProjectResourceSummary;

+ 1
- 1
src/app/(main)/dashboard/StaffUtilization/page.tsx Ver ficheiro

@@ -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 = () => {


+ 3
- 3
src/app/api/clientprojects/index.ts Ver ficheiro

@@ -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",


+ 53
- 0
src/app/api/resourcesummary/index.ts Ver ficheiro

@@ -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',
},
];

+ 15
- 3
src/components/CustomDatagrid/CustomDatagrid.tsx Ver ficheiro

@@ -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<CustomDatagridProps> = ({
@@ -32,6 +34,8 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
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<CustomDatagridProps> = ({
editMode="row"
checkboxSelection={checkboxSelection}
onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel}
initialState={{
pagination: { paginationModel: { pageSize: 10 } },
}}
@@ -222,6 +228,8 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
editMode="row"
checkboxSelection={checkboxSelection}
onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel}
initialState={{
pagination: { paginationModel: { pageSize: 10 } },
}}
@@ -251,6 +259,8 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
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<CustomDatagridProps> = ({
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<CustomDatagridProps> = ({
"& .MuiDataGrid-cell:hover": {
color: "primary.main",
},
height: 300,
height: dataGridHeight ?? 300,
"& .MuiDataGrid-root": {
overflow: "auto",
},


+ 6
- 0
src/components/NavigationContent/NavigationContent.tsx Ver ficheiro

@@ -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: <ViewWeekIcon />,
label: "Project Resource Summary",
path: "/dashboard/ProjectResourceSummary",
}
],
},
{


+ 2
- 1
src/components/ProgressByClient/ProgressByClient.tsx Ver ficheiro

@@ -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");


+ 18
- 3
src/components/ProgressByClientSearch/ProgressByClientSearch.tsx Ver ficheiro

@@ -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<Props> = ({ 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<Props> = ({ 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<Column<ClientProjectResult>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: onTaskClick,
buttonIcon: <EditNote />,
},
{ 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 (


+ 2
- 1
src/components/ProgressByTeam/ProgressByTeam.tsx Ver ficheiro

@@ -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");


+ 548
- 0
src/components/ProjectResourceSummary/ProjectResourceSummary.tsx Ver ficheiro

@@ -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 (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>
{row.task.length > 0 && (
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
)}
</TableCell>
<TableCell style={{fontSize:10}}>{row.stage}</TableCell>
<TableCell style={{fontSize:10}}>{row.taskCount}</TableCell>
<TableCell style={{fontSize:10}}>{row.g1Planned}</TableCell>
<TableCell style={{fontSize:10}}>{row.g1Actual}</TableCell>
<TableCell style={{fontSize:10}}>{row.g2Planned}</TableCell>
<TableCell style={{fontSize:10}}>{row.g2Actual}</TableCell>
<TableCell style={{fontSize:10}}>{row.g3Planned}</TableCell>
<TableCell style={{fontSize:10}}>{row.g3Actual}</TableCell>
<TableCell style={{fontSize:10}}>{row.g4Planned}</TableCell>
<TableCell style={{fontSize:10}}>{row.g4Actual}</TableCell>
<TableCell style={{fontSize:10}}>{row.g5Planned}</TableCell>
<TableCell style={{fontSize:10}}>{row.g5Actual}</TableCell>
<TableCell style={{fontSize:10}}>{row.totalPlanned}</TableCell>
<TableCell style={{fontSize:10}}>{row.totalActual}</TableCell>
</TableRow>
{row.task.map((taskRow:any) => (
<>
<TableRow style={{backgroundColor:"#f0f3f7"}}>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell colSpan={1}/>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.stage}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.taskCount}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g1Planned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g1Actual}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g2Planned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g2Actual}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g3Planned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g3Actual}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g4Planned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g4Actual}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g5Planned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.g5Actual}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.totalPlanned}</TableCell>
</Collapse>
</TableCell>
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}>
<Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}>
<TableCell style={{fontSize:10}} colSpan={1}>{taskRow.totalActual}</TableCell>
</Collapse>
</TableCell>
</TableRow>
</>
))}
{/* <TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0, borderStyle:"dotted"}} colSpan={15}>
<Collapse in={open} timeout="auto" unmountOnExit style={{borderStyle:"dotted", marginLeft:-17, marginRight:-17}}>
<Box sx={{ margin: 1 }}>
{row.task.map((taskRow:any) => (
<TableRow key={taskRow.stage}>
<TableCell style={{borderStyle:"dotted"}}/>
<TableCell style={{fontSize:10, borderStyle:"dotted"}}>{taskRow.stage}</TableCell>
<TableCell style={{fontSize:10, borderStyle:"dotted"}}>{taskRow.taskCount}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g1Planned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g1Actual}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g2Planned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g2Actual}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g3Planned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g3Actual}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g4Planned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g4Actual}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g5Planned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.g5Actual}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.totalPlanned}</TableCell>
<TableCell style={{fontSize:10}}>{taskRow.totalActual}</TableCell>
</TableRow>
))}
</Box>
</Collapse>
</TableCell>
</TableRow> */}
</React.Fragment>
);
}

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 (
<Grid item sm sx={{
'& .groupColor': {
backgroundColor: 'rgba(240, 240, 240, 0.55)',
},
'& .totalGroupColor': {
backgroundColor: 'rgba(218, 218, 245, 0.55)',
},
}}>
<Card className="mt-5">
<CardHeader className="text-slate-500" title="Project Information"/>
<div className="ml-6 mr-6">
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{fontSize:"1em", fontWeight:"bold"}}>
<u>
Project
</u>
</div>
<div style={{fontSize:"1em"}}>
{projectName}
</div>
</div>
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{ fontSize:"1em", fontWeight:"bold"}}>
<u>
Project Fee
</u>
</div>
<div style={{fontSize:"1em"}}>
HKD {projectFee}
</div>
</div>
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{ fontSize:"1em", fontWeight:"bold"}}>
<u>
Status
</u>
</div>
<div style={{fontSize:"1em"}}>
{status}
</div>
</div>
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{ fontSize:"1em", fontWeight:"bold"}}>
<u>
Planned Resources
</u>
</div>
<div style={{fontSize:"1em"}}>
{plannedResources} Manhours
</div>
</div>
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{ fontSize:"1em", fontWeight:"bold"}}>
<u>
Actual Resources Spent
</u>
</div>
<div style={{fontSize:"1em"}}>
{actualResourcesSpent} Manhours
</div>
</div>
<div style={{ display: "inline-block", width: "33%"}}>
<div style={{ fontSize:"1em", fontWeight:"bold"}}>
<u>
Remaining Resources
</u>
</div>
<div style={{fontSize:"1em"}}>
{remainingResources} Manhours
</div>
</div>
</div>
{/* <div style={{display:"inline-block",width:"99%",marginLeft:10}}>
<CustomDatagrid rows={projectResourcesRows} columns={columns2} columnWidth={200} dataGridHeight={480} pageSize={100} columnGroupingModel={columnGroupingModel} sx={{fontSize:10}}/>
</div> */}
<div style={{display:"inline-block",width:"99%",marginLeft:10, marginRight:10, marginTop:10}}>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center" colSpan={3}>
</TableCell>
<TableCell align="center" colSpan={2}>
G1
</TableCell>
<TableCell align="center" colSpan={2}>
G2
</TableCell>
<TableCell align="center" colSpan={2}>
G3
</TableCell>
<TableCell align="center" colSpan={2}>
G4
</TableCell>
<TableCell align="center" colSpan={2}>
G5
</TableCell>
<TableCell align="center" colSpan={2} style={{backgroundColor:"rgba(218, 218, 245, 0.55)"}}>
Total
</TableCell>
</TableRow>
<TableRow>
<TableCell style={{width:"5%"}}/>
<TableCell style={{fontSize:10, width:"20%"}}>Stage</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Task Count</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Planned</TableCell>
<TableCell style={{fontSize:10, width:"5%"}}>Actual</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<Row key={row.stage} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
</div>
</Card>
</Grid>
);
};

export default ProjectResourceSummary;

+ 1
- 0
src/components/ProjectResourceSummary/index.ts Ver ficheiro

@@ -0,0 +1 @@
export { default } from "./ProjectResourceSummary";

+ 75
- 0
src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearch.tsx Ver ficheiro

@@ -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<Omit<ResourceSummaryResult, "id">>;
type SearchParamNames = keyof SearchQuery;


const ProjectResourceSummarySearch: React.FC<Props> = ({ 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<SearchParamNames>[] = 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<Column<ResourceSummaryResult>[]>(
() => [
{
name: "id",
label: t("View"),
onClick: onTaskClick,
buttonIcon: <ArticleIcon />,
},
{ name: "projectCode", label: t("Project Code") },
{ name: "projectName", label: t("Project Name") },
{ name: "clientCodeAndName", label: t("Client Code And Name") },
],
[onTaskClick, t],
// [t],
);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log(query);
}}
/>
<SearchResults<ResourceSummaryResult>
items={filteredProjects}
columns={columns}
/>
<ProjectResourceSummary/>
</>
);
};

export default ProjectResourceSummarySearch;

+ 40
- 0
src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchLoading.tsx Ver ficheiro

@@ -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 (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default ProjectResourceSummarySearchLoading;

+ 20
- 0
src/components/ProjectResourceSummarySearch/ProjectResourceSummarySearchWrapper.tsx Ver ficheiro

@@ -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 <ProjectResourceSummarySearch projects={clentprojects} />;
};

ProjectResourceSummarySearchWrapper.Loading = ProjectResourceSummarySearchLoading;

export default ProjectResourceSummarySearchWrapper;



+ 1
- 0
src/components/ProjectResourceSummarySearch/index.ts Ver ficheiro

@@ -0,0 +1 @@
export { default } from "./ProjectResourceSummarySearchWrapper";

Carregando…
Cancelar
Guardar