diff --git a/src/app/(main)/dashboard/ProjectResourceConsumptionRanking/page.tsx b/src/app/(main)/dashboard/ProjectResourceConsumptionRanking/page.tsx new file mode 100644 index 0000000..052b861 --- /dev/null +++ b/src/app/(main)/dashboard/ProjectResourceConsumptionRanking/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 ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; +import { Suspense } from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Typography from "@mui/material/Typography"; +import ProjectResourceConsumptionRankingDisplay from "@/components/ProjectResourceConsumptionRanking"; +import { preloadClientProjects } from "@/app/api/clientprojects"; + +export const metadata: Metadata = { + title: "Project Resource Consumption Ranking", +}; + +const ProjectResourceConsumptionRanking: React.FC = () => { + preloadClientProjects(); + return ( + + + Project Resource Consumption Ranking + + + + ); +}; +export default ProjectResourceConsumptionRanking; diff --git a/src/app/api/cashflow/index.ts b/src/app/api/cashflow/index.ts index b597a5c..d946c8a 100644 --- a/src/app/api/cashflow/index.ts +++ b/src/app/api/cashflow/index.ts @@ -39,6 +39,8 @@ export interface CashFlowReceivableAndExpenditure { totalBudget: number; totalExpenditure: number; expenditureReceivable: number; + totalProjectFee: number; + invoicedPercentage: number; } export interface CashFlowAnticipatedChartResult { anticipateIncomeList: any[]; diff --git a/src/app/api/clientprojects/actions.ts b/src/app/api/clientprojects/actions.ts index 9de9ea5..3b7af13 100644 --- a/src/app/api/clientprojects/actions.ts +++ b/src/app/api/clientprojects/actions.ts @@ -21,14 +21,14 @@ export interface ClientSubsidiaryProjectResult { comingPaymentMilestone: string; } -export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, subsidiaryId?: number) => { +export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { if (subsidiaryId === 0){ return serverFetchJson( - `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}` + `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` ); } else { return serverFetchJson( - `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}` + `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}&tableSorting=${tableSorting}` ); } diff --git a/src/app/api/teamprojects/actions.ts b/src/app/api/teamprojects/actions.ts index 2460a6b..926d05a 100644 --- a/src/app/api/teamprojects/actions.ts +++ b/src/app/api/teamprojects/actions.ts @@ -21,9 +21,43 @@ export interface ClientSubsidiaryProjectResult { comingPaymentMilestone: string; } -export const fetchAllTeamProjects = cache(async (teamLeadId: number) => { +export interface TeamConsumptionResult { + color: string; + team: string; + teamLead: string; + budgetedManhour: number; + spentManhour: number; + remainedManhour: number; + manhourConsumptionPercentage: number; +} + +export interface TeamProjectResult { + id: number; + teamId: number; + teamLeadId: number; + teamCode: string; + teamName: string; + projectNo: number; +} + +export const fetchTeamProjects = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/dashboard/searchTeamProjectNo`); +}); + +export const fetchAllTeamProjects = cache(async (teamLeadId: number, tableSorting:string) => { return serverFetchJson( - `${BASE_API_URL}/dashboard/searchTeamProject?teamLeadId=${teamLeadId}` + `${BASE_API_URL}/dashboard/searchTeamProject?teamLeadId=${teamLeadId}&tableSorting=${tableSorting}` ); }); + +export const fetchAllTeamConsumption = cache(async (teamIdList: number[],tableSorting:string) => { + if (teamIdList.length !== 0) { + const queryParams = new URLSearchParams(); + queryParams.append('teamIdList', teamIdList.join(',')); + return serverFetchJson(`${BASE_API_URL}/dashboard/searchTeamConsumption?${queryParams.toString()}&tableSorting=${tableSorting}`); + } else { + return []; + } + +}); diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index be64dbb..cec5d05 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -21,6 +21,7 @@ const pathToLabelMap: { [path: string]: string } = { "/dashboard/ProjectResourceSummary": "Project Resource Summary", "/dashboard/ProjectStatusByClient": "Project Status by Client", "/dashboard/ProjectStatusByTeam": "Project Status by Team", + "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", "/dashboard/StaffUtilization": "Staff Utilization", "/projects": "Projects", "/projects/create": "Create Project", diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 7e683a4..dc53910 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -127,6 +127,11 @@ const NavigationContent: React.FC = ({ abilities, username }) => { label: "Project Status by Team", path: "/dashboard/ProjectStatusByTeam", }, + { + icon: , + label: "Project Resource Consumption Ranking", + path: "/dashboard/ProjectResourceConsumptionRanking", + }, { icon: , label: "Staff Utilization", diff --git a/src/components/ProgressByClient/ProgressByClient.tsx b/src/components/ProgressByClient/ProgressByClient.tsx index c86f3a2..f22d7d3 100644 --- a/src/components/ProgressByClient/ProgressByClient.tsx +++ b/src/components/ProgressByClient/ProgressByClient.tsx @@ -59,6 +59,9 @@ const ProgressByClient: React.FC = () => { const [receiptToDate, setReceiptToDate] = useState(null); const [selectedRows, setSelectedRows]:any[] = useState([]); const [chartProjectName, setChartProjectName]:any[] = useState([]); + const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); + const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); + const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", @@ -86,6 +89,7 @@ const ProgressByClient: React.FC = () => { "#d4feed", "#0ab2ff", "#ff88a2", "#4fda21", "#cefb2f", "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; const [clientSubsidiaryProjectResult, setClientSubsidiaryProjectResult]:any[] = useState([]); + const [tableSorting, setTableSorting] = useState('ProjectName'); const fetchData = async () => { if (customerId && subsidiaryId) { @@ -93,12 +97,13 @@ const ProgressByClient: React.FC = () => { if (subsidiaryId === '-'){ console.log("ss") const clickResult = await fetchAllClientSubsidiaryProjects( - Number(customerId),Number(0)) + Number(customerId),tableSorting,Number(0)) console.log(clickResult) setClientSubsidiaryProjectResult(clickResult); } else { const clickResult = await fetchAllClientSubsidiaryProjects( Number(customerId), + tableSorting, Number(subsidiaryId)) console.log(clickResult) setClientSubsidiaryProjectResult(clickResult); @@ -113,19 +118,28 @@ const ProgressByClient: React.FC = () => { useEffect(() => { const projectCode = [] + const projectName = [] + const projectBudgetedManHour = [] + const projectSpentManHour = [] const manhourConsumptionPercentage = [] for (let i = 0; i < clientSubsidiaryProjectResult.length; i++){ clientSubsidiaryProjectResult[i].color = color[i] - projectCode.push(clientSubsidiaryProjectResult[i].projectCode) + projectCode.push(clientSubsidiaryProjectResult[i].projectCode + "(" + clientSubsidiaryProjectResult[i].team + ")") + projectName.push(clientSubsidiaryProjectResult[i].projectName) + projectBudgetedManHour.push(clientSubsidiaryProjectResult[i].budgetedManhour) + projectSpentManHour.push(clientSubsidiaryProjectResult[i].spentManhour) manhourConsumptionPercentage.push(clientSubsidiaryProjectResult[i].manhourConsumptionPercentage) } setChartProjectName(projectCode) + setChartProjectDisplayName(projectName) + setChartProjectBudgetedHour(projectBudgetedManHour) + setChartProjectSpentHour(projectSpentManHour) setChartManhourConsumptionPercentage(manhourConsumptionPercentage) }, [clientSubsidiaryProjectResult]); useEffect(() => { fetchData() - }, [customerId,subsidiaryId]); + }, [customerId,subsidiaryId,tableSorting]); @@ -383,7 +397,31 @@ const ProgressByClient: React.FC = () => { const options: ApexOptions = { chart: { type: "bar", - height: 350, + height: 450, + }, + tooltip: { + enabled: true, // Enable tooltip + custom: ({ series, seriesIndex, dataPointIndex, w }) => { + + const projectCode = chartProjectName[dataPointIndex]; + const projectName = chartProjectDisplayName[dataPointIndex]; + const budgetManhours = chartProjectBudgetedHour[dataPointIndex]; + const spentManhours = chartProjectSpentHour[dataPointIndex]; + const value = series[seriesIndex][dataPointIndex]; + const tooltipContent = ` +
+ ${projectCode} - ${projectName} +
+ Budget Manhours: ${budgetManhours} hours +
+ Spent Manhours: ${spentManhours} hours +
+ Percentage: ${value}% +
+ `; + + return tooltipContent; + }, }, series: [{ name: "Project Resource Consumption Percentage", @@ -418,10 +456,15 @@ const ProgressByClient: React.FC = () => { bar: { horizontal: true, distributed: true, + dataLabels: { + position: 'top' + }, }, }, dataLabels: { - enabled: false, + enabled: true, + textAnchor: 'end', + formatter: (val) => `${val}%`, }, xaxis: { categories: chartProjectName, @@ -443,6 +486,11 @@ const ProgressByClient: React.FC = () => { }, grid: { borderColor: "#f1f1f1", + xaxis: { + lines: { + show: true, + } + } }, annotations: {}, }; @@ -517,11 +565,35 @@ const ProgressByClient: React.FC = () => {
+
+ + Sorting: + +
+
+ + + +
{/*
@@ -533,6 +605,8 @@ const ProgressByClient: React.FC = () => { ); })}
*/} +
+ = () => { marginLeft: 0, }} > - - + + {percentageArray.length === 0 && (
Please select the project you want to check. @@ -580,12 +654,13 @@ const ProgressByClient: React.FC = () => { options={options2} series={percentageArray} type="donut" + style={{marginTop:'10rem'}} /> )} - +
{ const [receiptToDate, setReceiptToDate] = useState(null); const [selectedRows, setSelectedRows] = useState([]); const [chartProjectName, setChartProjectName]:any[] = useState([]); + const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); + const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); + const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", @@ -77,16 +80,21 @@ const ProgressByTeam: React.FC = () => { "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; const [teamProjectResult, setTeamProjectResult]:any[] = useState([]); const [currentPageProjectList, setCurrentPageProjectList]: any[] = React.useState([]); + const [currentPageProjectNameList, setCurrentPageProjectNameList]: any[] = React.useState([]); + const [currentPageProjectBudgetedManhourList, setCurrentPageProjectBudgetedManhourList]: any[] = React.useState([]); + const [currentPageProjectSpentManhourList, setCurrentPageProjectSpentManhourList]: any[] = React.useState([]); const [currentPagePercentage, setCurrentPagePercentage]: any[] = React.useState([]); const [currentPageColor, setCurrentPageColor]: any[] = React.useState([]); const [currentPage, setCurrentPage] = useState(1); const recordsPerPage = 10; + const [tableSorting, setTableSorting] = useState('ProjectName'); const fetchData = async () => { + console.log(tableSorting) if (teamLeadId) { try { const clickResult = await fetchAllTeamProjects( - Number(teamLeadId)) + Number(teamLeadId),tableSorting) console.log(clickResult) setTeamProjectResult(clickResult); } catch (error) { @@ -97,19 +105,29 @@ const ProgressByTeam: React.FC = () => { useEffect(() => { const projectNo = [] + const projectName = [] + const projectBudgetedManHour = [] + const projectSpentManHour = [] const manhourConsumptionPercentage = [] for (let i = 0; i < teamProjectResult.length; i++){ teamProjectResult[i].color = color[i] - projectNo.push(teamProjectResult[i].projectCode) + console.log(teamProjectResult[i]) + projectNo.push(teamProjectResult[i].projectCode + "(" + teamProjectResult[i].team + ")") + projectName.push(teamProjectResult[i].projectName) + projectBudgetedManHour.push(teamProjectResult[i].budgetedManhour) + projectSpentManHour.push(teamProjectResult[i].spentManhour) manhourConsumptionPercentage.push(teamProjectResult[i].manhourConsumptionPercentage) } setChartProjectName(projectNo) + setChartProjectDisplayName(projectName) + setChartProjectBudgetedHour(projectBudgetedManHour) + setChartProjectSpentHour(projectSpentManHour) setChartManhourConsumptionPercentage(manhourConsumptionPercentage) }, [teamProjectResult]); useEffect(() => { fetchData() - }, [teamLeadId]); + }, [teamLeadId,tableSorting]); const rows = [ { @@ -462,7 +480,31 @@ const ProgressByTeam: React.FC = () => { const options: ApexOptions = { chart: { type: "bar", - height: 350, + height: 450, + }, + tooltip: { + enabled: true, // Enable tooltip + custom: ({ series, seriesIndex, dataPointIndex, w }) => { + + const projectCode = currentPageProjectList[dataPointIndex]; + const projectName = currentPageProjectNameList[dataPointIndex]; + const budgetManhours = currentPageProjectBudgetedManhourList[dataPointIndex]; + const spentManhours = currentPageProjectSpentManhourList[dataPointIndex]; + const value = series[seriesIndex][dataPointIndex]; + const tooltipContent = ` +
+ ${projectCode} - ${projectName} +
+ Budget Manhours: ${budgetManhours} hours +
+ Spent Manhours: ${spentManhours} hours +
+ Percentage: ${value}% +
+ `; + + return tooltipContent; + }, }, series: [{ name: "Project Resource Consumption Percentage", @@ -473,10 +515,15 @@ const ProgressByTeam: React.FC = () => { bar: { horizontal: true, distributed: true, + dataLabels: { + position: 'top' + }, }, }, dataLabels: { - enabled: false, + enabled: true, + textAnchor: 'end', + formatter: (val) => `${val}%`, }, xaxis: { categories: currentPageProjectList, @@ -498,6 +545,11 @@ const ProgressByTeam: React.FC = () => { }, grid: { borderColor: "#f1f1f1", + xaxis: { + lines: { + show: true, + } + } }, annotations: {}, }; @@ -564,11 +616,17 @@ const ProgressByTeam: React.FC = () => { useEffect(() => { console.log(chartManhourConsumptionPercentage) const currentPageProjectData = chartProjectName.slice(startIndex, endIndex) + const currentPageProjectName = chartProjectDisplayName.slice(startIndex, endIndex) + const currentPageProjectBudgetedManhour = chartProjectBudgetedHour.slice(startIndex, endIndex) + const currentPageProjectSpentManhour = chartProjectSpentHour.slice(startIndex, endIndex) const currentPageData = chartManhourConsumptionPercentage.slice(startIndex, endIndex); const colorArray = color.slice(startIndex, endIndex); console.log(currentPage) console.log(Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)) setCurrentPageProjectList(currentPageProjectData) + setCurrentPageProjectNameList(currentPageProjectName) + setCurrentPageProjectBudgetedManhourList(currentPageProjectBudgetedManhour) + setCurrentPageProjectSpentManhourList(currentPageProjectSpentManhour) setCurrentPagePercentage(currentPageData) setCurrentPageColor(colorArray) }, [chartManhourConsumptionPercentage,currentPage]); @@ -598,11 +656,35 @@ const ProgressByTeam: React.FC = () => {
+
+ + Sorting: + +
+
+ + + +
{currentPage === 1 && ( @@ -646,6 +728,8 @@ const ProgressByTeam: React.FC = () => { ); })}
*/} + + { marginLeft: 0, }} > - - + + {percentageArray.length === 0 && (
Please select the project you want to check.
@@ -693,12 +777,13 @@ const ProgressByTeam: React.FC = () => { options={options2} series={percentageArray} type="donut" + style={{marginTop:'10rem'}} /> )}
- +
{ const [monthlyChartRightMax, setMonthlyChartRightMax]: any[] = React.useState(10); const [monthlyAnticipateLeftMax, setMonthlyAnticipateLeftMax]: any[] = React.useState(10); const [receivedPercentage,setReceivedPercentage]: any[] = React.useState(0); + const [invoicedPercentage,setInvoicedPercentage]: any[] = React.useState(0); + const [totalFee,setTotalFee]: any[] = React.useState(0); const [totalBudget,setTotalBudget]: any[] = React.useState(0); const [totalInvoiced,setTotalInvoiced]: any[] = React.useState(0); const [totalReceived,setTotalReceived]: any[] = React.useState(0); @@ -121,6 +124,8 @@ const ProjectCashFlow: React.FC = () => { const cashFlowReceivableAndExpenditureData = await fetchProjectsCashFlowReceivableAndExpenditure(selectedProjectIdList); if(cashFlowReceivableAndExpenditureData.length !== 0){ setReceivedPercentage(cashFlowReceivableAndExpenditureData[0].receivedPercentage) + setInvoicedPercentage(cashFlowReceivableAndExpenditureData[0].invoicedPercentage) + setTotalFee(cashFlowReceivableAndExpenditureData[0].totalProjectFee) setTotalInvoiced(cashFlowReceivableAndExpenditureData[0].totalInvoiced) setTotalReceived(cashFlowReceivableAndExpenditureData[0].totalReceived) setReceivable(cashFlowReceivableAndExpenditureData[0].receivable) @@ -511,9 +516,9 @@ const ProjectCashFlow: React.FC = () => { const accountsReceivableOptions: ApexOptions = { colors: ["#20E647"], - series: [receivedPercentage], + series: [receivedPercentage,invoicedPercentage], chart: { - height: 350, + height: 50, type: "radialBar", }, plotOptions: { @@ -533,13 +538,23 @@ const ProjectCashFlow: React.FC = () => { }, dataLabels: { name: { - show: false, + show: true, + fontSize: "0.9em", }, value: { color: "#3e98c7", - fontSize: "3em", + fontSize: "1.5em", show: true, }, + total: { + show: true, + color: "#20E647", + fontSize: "0.9em", + label: 'Receivable / Invoiced', + formatter: function (w:any) { + return receivedPercentage + "% / " + invoicedPercentage + "%" + }, + } }, }, }, @@ -555,7 +570,7 @@ const ProjectCashFlow: React.FC = () => { stroke: { lineCap: "round", }, - labels: ["AccountsReceivable"], + labels: ["Accounts Receivable","Account Invoiced"], }; const expenditureOptions: ApexOptions = { @@ -582,11 +597,12 @@ const ProjectCashFlow: React.FC = () => { }, dataLabels: { name: { - show: false, + show: true, + fontSize: "0.9em", }, value: { color: "#3e98c7", - fontSize: "3em", + fontSize: "1.5em", show: true, }, }, @@ -604,7 +620,7 @@ const ProjectCashFlow: React.FC = () => { stroke: { lineCap: "round", }, - labels: ["AccountsReceivable"], + labels: ["Accounts Expenditure"], }; const rows = [ @@ -812,12 +828,26 @@ const ProjectCashFlow: React.FC = () => { className="text-slate-500" title="Accounts Receivable (HKD)" /> +
+
+
+ Total Project Fee +
+
+ ${totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
= ({ TotalBudget, TotalCumulative, TotalInvoicedAmount, + TotalUnInvoicedAmount, TotalReceivedAmount, CashFlowStatus, CostPerformanceIndex, ClickedIndex, + ProjectedCPI, + ProjectedCashFlowStatus, Index, }) => { const [SearchCriteria, setSearchCriteria] = React.useState({}); @@ -106,6 +112,13 @@ const ProjectFinancialCard: React.FC = ({ {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

+
+ Total Un-Invoiced Amount +
+
+ {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+
Total Received Amount
@@ -164,6 +177,57 @@ const ProjectFinancialCard: React.FC = ({
)} +
+ Projected Cash Flow Status +
+ {ProjectedCashFlowStatus === "Negative" && ( + <> +
+ {ProjectedCashFlowStatus} +
+
+ + )} + {ProjectedCashFlowStatus === "Positive" && ( + <> +
+ {CashFlowStatus} +
+
+ + )} +
+ Projected Cost Performance Index (CPI) +
+ {Number(ProjectedCPI) < 1 && ( + <> +
+ {ProjectedCPI} +
+ + )} + {Number(ProjectedCPI) >= 1 && ( + <> +
+ {ProjectedCPI} +
+ + )} ); }; diff --git a/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx index 02d56e4..cab3985 100644 --- a/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx +++ b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx @@ -133,6 +133,40 @@ const ProjectFinancialSummary: React.FC = () => { } }, }, + { + id: 'projectedCashFlowStatus', + field: 'projectedCashFlowStatus', + headerName: "Projected Cash Flow Status", + minWidth:100, + renderCell: (params:any) => { + if (params.row.projectedCashFlowStatus === "Positive") { + return ( + {params.row.projectedCashFlowStatus} + ) + } else if (params.row.projectedCashFlowStatus === "Negative") { + return ( + {params.row.projectedCashFlowStatus} + ) + } + }, + }, + { + id: 'projectedCpi', + field: 'projectedCpi', + headerName: "Projected CPI", + minWidth:50, + renderCell: (params:any) => { + if (params.row.projectedCpi >= 1) { + return ( + {params.row.projectedCpi} + ) + } else if (params.row.projectedCpi < 1) { + return ( + {params.row.projectedCpi} + ) + } + }, + }, { id: 'totalFee', field: 'totalFee', @@ -292,86 +326,119 @@ const columns2 = [ } }, }, - { - id: "cpi", - field: "cpi", - headerName: "CPI", - minWidth:50, - renderCell: (params: any) => { - if (params.row.cpi >= 1) { - return {params.row.cpi}; - } else if (params.row.cpi < 1) { - return {params.row.cpi}; - } - }, + { + id: "cpi", + field: "cpi", + headerName: "CPI", + minWidth:50, + 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)", - minWidth:50, - renderCell: (params:any) => { + }, + { + id: 'projectedCashFlowStatus', + field: 'projectedCashFlowStatus', + headerName: "Projected Cash Flow Status", + minWidth:100, + renderCell: (params:any) => { + if (params.row.projectedCashFlowStatus === "Positive") { + return ( + {params.row.projectedCashFlowStatus} + ) + } else if (params.row.projectedCashFlowStatus === "Negative") { + return ( + {params.row.projectedCashFlowStatus} + ) + } + }, + }, + { + id: 'projectedCpi', + field: 'projectedCpi', + headerName: "Projected CPI", + minWidth:50, + renderCell: (params:any) => { + if (params.row.projectedCpi >= 1) { + return ( + {params.row.projectedCpi} + ) + } else if (params.row.projectedCpi < 1) { + return ( + {params.row.projectedCpi} + ) + } + }, + }, + { + id: 'totalFees', + field: 'totalFees', + headerName: "Total Fees (HKD)", + minWidth:50, + renderCell: (params:any) => { + return ( + ${params.row.totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ) + }, + }, + { + id: 'totalBudget', + field: 'totalBudget', + headerName: "Total Budget (HKD)", + minWidth:50, + renderCell: (params:any) => { return ( - ${params.row.totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ${params.row.totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ) }, -}, -{ - id: 'totalBudget', - field: 'totalBudget', - headerName: "Total Budget (HKD)", - minWidth:50, - renderCell: (params:any) => { - return ( - ${params.row.totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ) -}, -}, -{ - id: 'totalCumulativeExpenditure', - field: 'totalCumulativeExpenditure', - headerName: "Total Cumulative Expenditure (HKD)", - minWidth:250, - renderCell: (params:any) => { - return ( - ${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ) }, -}, -{ - id: 'totalInvoicedAmount', - field: 'totalInvoicedAmount', - headerName: "Total Invoiced Amount (HKD)", - minWidth:250, - renderCell: (params:any) => { - return ( - ${params.row.totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ) + { + id: 'totalCumulativeExpenditure', + field: 'totalCumulativeExpenditure', + headerName: "Total Cumulative Expenditure (HKD)", + minWidth:250, + renderCell: (params:any) => { + return ( + ${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ) + }, }, -}, -{ - id: 'totalUnInvoicedAmount', - field: 'totalUnInvoicedAmount', - headerName: "Total Un-invoiced Amount (HKD)", - minWidth:250, - renderCell: (params:any) => { - return ( - ${params.row.totalUninvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ) + { + id: 'totalInvoicedAmount', + field: 'totalInvoicedAmount', + headerName: "Total Invoiced Amount (HKD)", + minWidth:250, + renderCell: (params:any) => { + return ( + ${params.row.totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ) + }, + }, + { + id: 'totalUnInvoicedAmount', + field: 'totalUnInvoicedAmount', + headerName: "Total Un-invoiced Amount (HKD)", + minWidth:250, + renderCell: (params:any) => { + return ( + ${params.row.totalUninvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ) + }, }, -}, -{ - id: 'totalReceivedAmount', - field: 'totalReceivedAmount', - headerName: "Total Received Amount (HKD)", - minWidth:250, - renderCell: (params:any) => { - return ( - ${params.row.totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ) + { + id: 'totalReceivedAmount', + field: 'totalReceivedAmount', + headerName: "Total Received Amount (HKD)", + minWidth:250, + renderCell: (params:any) => { + return ( + ${params.row.totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ) + }, }, -}, ]; const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { @@ -398,7 +465,7 @@ const columns2 = [
{projectFinancialData.map((record:any, index:any) => (
handleCardClick(record,index)}> - +
))}
diff --git a/src/components/ProjectResourceConsumptionRanking/ProjectResourceConsumptionRanking.tsx b/src/components/ProjectResourceConsumptionRanking/ProjectResourceConsumptionRanking.tsx new file mode 100644 index 0000000..340f856 --- /dev/null +++ b/src/components/ProjectResourceConsumptionRanking/ProjectResourceConsumptionRanking.tsx @@ -0,0 +1,923 @@ +"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 ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; +import { Suspense } from "react"; +import { useSearchParams } from 'next/navigation'; +import { fetchAllTeamProjects, TeamProjectResult, fetchTeamProjects, fetchAllTeamConsumption} from "@/app/api/teamprojects/actions"; +// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface Props { + projects: TeamProjectResult[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ProjectResourceConsumptionRanking: React.FC = () => { + const searchParams = useSearchParams(); + const teamLeadId = searchParams.get('teamLeadId'); + const [activeTab, setActiveTab] = useState("financialSummary"); + const [SearchCriteria, setSearchCriteria] = React.useState({}); + const { t } = useTranslation("dashboard"); + const [projectData, setProjectData]: any[] = React.useState([]); + const [filteredResult, setFilteredResult]:any[] = useState([]); + const [teamCode, setTeamCode] = useState(""); + const [teamName, setTeamName] = useState(""); + const [projectArray, setProjectArray]: any[] = useState([]); + const [percentageArray, setPercentageArray]: any[] = useState([]); + const [colorArray, setColorArray]: any[] = useState([]); + const [selectionModel, setSelectionModel]: any[] = React.useState([]); + const [pieChartColor, setPieChartColor]: any[] = React.useState([]); + const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); + const [projectBudgetManhour, setProjectBudgetManhour]: any = + React.useState("-"); + const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); + const [remainedManhour, setRemainedManhour]: any = React.useState("-"); + const [lastUpdate, setLastUpdate]: any = React.useState("-"); + const [dropdownDemo, setDropdownDemo] = useState(""); + const [dateDemo, setDateDemo] = useState(null); + const [checkboxDemo, setCheckboxDemo] = useState(false); + const [receiptFromDate, setReceiptFromDate] = useState(null); + const [receiptToDate, setReceiptToDate] = useState(null); + const [selectedRows, setSelectedRows] = useState([]); + const [chartProjectName, setChartProjectName]:any[] = useState([]); + const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); + const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); + const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); + const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); + const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", + "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", + "#f595a6", "#88f1cc", "#9dcff5", "#a39bf5", "#f8de83", + "#f5a0b1", "#82eec7", "#a8d4f1", "#9f9ef1", "#f6ea7f", + "#f5abb4", "#7cebca", "#b3d9ed", "#9ba1ed", "#f4f67b", + "#f5b6b7", "#76e8cd", "#bed6e9", "#97a4e9", "#f2fa77", + "#f5c1ba", "#70e5d0", "#c9d3e5", "#93a7e5", "#f0fe73", + "#f5ccbd", "#6ae2d3", "#d4d0e1", "#8faae1", "#eefe6f", + "#f5d7c0", "#64dfd6", "#dfc5dd", "#8badd5", "#ecfe6b", + "#f5e2c3", "#5edcd9", "#eabada", "#87b0c9", "#eafc67", + "#f5edc6", "#58d9dc", "#f5afd6", "#83b3bd", "#e8fc63", + "#f5f8c9", "#52d6df", "#ffacd2", "#7fb6b1", "#e6fc5f", + "#f5ffcc", "#4cd3e2", "#ffa9ce", "#7bb9a5", "#e4fc5b", + "#f2ffcf", "#46d0e5", "#ffa6ca", "#77bc99", "#e2fc57", + "#efffd2", "#40cde8", "#ffa3c6", "#73bf8d", "#e0fc53", + "#ecffd5", "#3acaeb", "#ffa0c2", "#6fc281", "#defb4f", + "#e9ffd8", "#34c7ee", "#ff9dbe", "#6bc575", "#dcfb4b", + "#e6ffdb", "#2ec4f1", "#ff9aba", "#67c869", "#dafb47", + "#e3ffde", "#28c1f4", "#ff97b6", "#63cb5d", "#d8fb43", + "#e0ffe1", "#22bef7", "#ff94b2", "#5fce51", "#d6fb3f", + "#ddfee4", "#1cbbfa", "#ff91ae", "#5bd145", "#d4fb3b", + "#dafee7", "#16b8fd", "#ff8eaa", "#57d439", "#d2fb37", + "#d7feea", "#10b5ff", "#ff8ba6", "#53d72d", "#d0fb33", + "#d4feed", "#0ab2ff", "#ff88a2", "#4fda21", "#cefb2f", + "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; + const [teamProjectResult, setTeamProjectResult]:any[] = useState([]); + const [currentPageProjectList, setCurrentPageProjectList]: any[] = React.useState([]); + const [currentPageProjectNameList, setCurrentPageProjectNameList]: any[] = React.useState([]); + const [currentPageProjectBudgetedManhourList, setCurrentPageProjectBudgetedManhourList]: any[] = React.useState([]); + const [currentPageProjectSpentManhourList, setCurrentPageProjectSpentManhourList]: any[] = React.useState([]); + const [currentPagePercentage, setCurrentPagePercentage]: any[] = React.useState([]); + const [currentPageColor, setCurrentPageColor]: any[] = React.useState([]); + const [currentPage, setCurrentPage] = useState(1); + const recordsPerPage = 10; + const [tableSorting, setTableSorting] = useState('ProjectName'); + const [selectedTeamIdList, setSelectedTeamIdList]: any[] = React.useState([]); + + const fetchTeamData = async () => { + const teamprojects = await fetchTeamProjects(); + setProjectData(teamprojects) + setFilteredResult(teamprojects) + } + + const fetchData = async () => { + console.log(selectedTeamIdList) + if (selectedTeamIdList) { + try { + const clickResult = await fetchAllTeamConsumption( + selectedTeamIdList,tableSorting) + console.log(clickResult) + setTeamProjectResult(clickResult); + } catch (error) { + console.error('Error fetching team consumption:', error); + } + } + } + + const searchCriteria: Criterion[] = useMemo( + () => [ + { label: "Team Code", paramName: "teamCode", type: "text" }, + { label: "Team Name", paramName: "teamName", type: "text" }, + ], + [t], + ); + + useEffect(() => { + const projectNo = [] + const projectName = [] + const projectBudgetedManHour = [] + const projectSpentManHour = [] + const manhourConsumptionPercentage = [] + for (let i = 0; i < teamProjectResult.length; i++){ + teamProjectResult[i].color = color[i] + console.log(teamProjectResult[i]) + projectNo.push(teamProjectResult[i].projectCode + "(" + teamProjectResult[i].team + ")") + projectName.push(teamProjectResult[i].projectName) + projectBudgetedManHour.push(teamProjectResult[i].budgetedManhour) + projectSpentManHour.push(teamProjectResult[i].spentManhour) + manhourConsumptionPercentage.push(teamProjectResult[i].manhourConsumptionPercentage) + } + setChartProjectName(projectNo) + setChartProjectDisplayName(projectName) + setChartProjectBudgetedHour(projectBudgetedManHour) + setChartProjectSpentHour(projectSpentManHour) + setChartManhourConsumptionPercentage(manhourConsumptionPercentage) + }, [teamProjectResult]); + + useEffect(() => { + fetchTeamData() + }, []); + + useEffect(() => { + fetchData() + }, [selectedTeamIdList,tableSorting]); + + const rows = [ + { + id: 1, + teamCode: "TEAM-001", + teamName: "Team A", + noOfProjects: "5", + }, + { + id: 2, + teamCode: "TEAM-001", + teamName: "Team B", + noOfProjects: "5", + }, + { + id: 3, + teamCode: "TEAM-001", + teamName: "Team C", + noOfProjects: "3", + }, + { + id: 4, + teamCode: "TEAM-001", + teamName: "Team D", + noOfProjects: "1", + }, + ]; + //['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'] + const rows2 = [ + { + id: 1, + project: "Consultancy Project 123", + team: "XXX", + teamLeader: "XXX", + currentStage: "Contract Documentation", + budgetedManhour: "200.00", + spentManhour: "120.00", + remainedManhour: "80.00", + comingPaymentMilestone: "31/03/2024", + alert: false, + color: "#f57f90", + }, + { + id: 2, + project: "Consultancy Project 456", + team: "XXX", + teamLeader: "XXX", + currentStage: "Report Preparation", + budgetedManhour: "400.00", + spentManhour: "200.00", + remainedManhour: "200.00", + comingPaymentMilestone: "20/02/2024", + alert: false, + color: "#94f7d6", + }, + { + id: 3, + project: "Construction Project A", + team: "YYY", + teamLeader: "YYY", + currentStage: "Construction", + budgetedManhour: "187.50", + spentManhour: "200.00", + remainedManhour: "12.50", + comingPaymentMilestone: "13/12/2023", + alert: true, + color: "#87c5f5", + }, + { + id: 4, + project: "Construction Project B", + team: "XXX", + teamLeader: "XXX", + currentStage: "Post Construction", + budgetedManhour: "100.00", + spentManhour: "40.00", + remainedManhour: "60.00", + comingPaymentMilestone: "05/01/2024", + alert: false, + color: "#ab95f5", + }, + { + id: 5, + project: "Construction Project C", + team: "YYY", + teamLeader: "YYY", + currentStage: "Construction", + budgetedManhour: "300.00", + spentManhour: "150.00", + remainedManhour: "150.00", + comingPaymentMilestone: "31/03/2024", + alert: false, + color: "#fcd68b", + }, + ]; + + const searchColumns = [ + { + id: "teamCode", + field: "teamCode", + headerName: "Team Code", + flex: 1, + }, + { + id: "teamName", + field: "teamName", + headerName: "Team Name", + flex: 1, + }, + { + id: "projectNo", + field: "projectNo", + headerName: "No. of Projects", + flex: 1, + }, + ]; + + const columns = [ + { + id: "clientCode", + field: "clientCode", + headerName: "Client Code", + flex: 1, + }, + { + id: "clientName", + field: "clientName", + headerName: "Client Name", + flex: 1, + }, + { + id: "clientSubsidiaryCode", + field: "clientSubsidiaryCode", + headerName: "Client Subsidiary Code", + flex: 1, + }, + { + id: "noOfProjects", + field: "noOfProjects", + headerName: "No. of Projects", + flex: 1, + }, + ]; + + const columns2 = [ + { + id: "color", + field: "color", + headerName: "", + renderCell: (params: any) => { + return ( + + ); + }, + flex: 0.1, + }, + { + id: "projectCode", + field: "projectCode", + headerName: "Project No", + minWidth:100 + }, + { + id: "projectName", + field: "projectName", + headerName: "Project", + minWidth:300 + }, + { + id: "team", + field: "team", + headerName: "Team", + minWidth:50 + }, + { + id: "teamLead", + field: "teamLead", + headerName: "Team Leader", + minWidth: 70 + }, + { + id: "expectedStage", + field: "expectedStage", + headerName: "Expected Stage", + minWidth: 300, + renderCell: (params: any) => { + if (params.row.expectedStage != null){ + const expectedStage = params.row.expectedStage; + const lines = expectedStage.split(",").map((line:any, index:any) => ( + + {line.trim()} +
+
+ )); + return
{lines}
; + } else { + return
-
; + } + + }, + }, + { + id: "budgetedManhour", + field: "budgetedManhour", + headerName: "Budgeted Manhour", + minWidth: 70, + renderCell: (params: any) => { + return {params.row.budgetedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}; + } + }, + { + id: "spentManhour", + field: "spentManhour", + headerName: "Spent Manhour", + renderCell: (params: any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return ( + {params.row.spentManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + ); + } else { + return {params.row.spentManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}; + } + }, + minWidth: 70 + }, + { + id: "remainedManhour", + field: "remainedManhour", + headerName: "Remained Manhour", + renderCell: (params: any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return ( + ({params.row.remainedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}) + ); + } else { + return {params.row.remainedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}; + } + }, + minWidth: 70 + }, + { + id: "comingPaymentMilestone", + field: "comingPaymentMilestone", + headerName: "Coming Payment Milestone", + minWidth: 100 + }, + { + id: "alert", + field: "alert", + headerName: "Alert", + renderCell: (params: any) => { + if (params.row.alert === true) { + return ( + + + + ); + } else { + return ; + } + }, + flex: 0.1, + }, + ]; + + const InputFields = [ + { + id: "teamCode", + label: "Team Code", + type: "text", + value: teamCode, + setValue: setTeamCode, + }, + { + id: "teamName", + label: "Team Name", + type: "text", + value: teamName, + setValue: setTeamName, + }, + + // { id: 'dropdownDemo', label: "dropdownDemo", type: 'dropdown', options: [{id:"1", label:"1"}], value: dropdownDemo, setValue: setDropdownDemo }, + // { id: 'dateDemo', label:'dateDemo', type: 'date', value: dateDemo, setValue: setDateDemo }, + // { id: 'checkboxDemo', label:'checkboxDemo', type: 'checkbox', value: checkboxDemo, setValue: setCheckboxDemo }, + // { id: ['receiptFromDate','receiptToDate'], label: ["收貨日期","收貨日期"], value: [receiptFromDate ? receiptFromDate : null, receiptToDate ? receiptToDate : null], + // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, + ]; + + const stageDeadline = [ + "31/03/2024", + "20/02/2024", + "01/12/2023", + "05/01/2024", + "31/03/2023", + ]; + + const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [ + { + data: [17.1, 28.6, 5.7, 48.6], + }, + ]; + + const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [ + { + name: "Project Resource Consumption Percentage", + data: [80, 55, 40, 65, 70], + }, + ]; + + const options2: ApexOptions = { + chart: { + type: "donut", + }, + colors: colorArray, + plotOptions: { + pie: { + donut: { + labels: { + show: true, + name: { + show: true, + }, + value: { + show: true, + fontWeight: 500, + fontSize: "30px", + color: "#3e98c7", + }, + total: { + show: true, + showAlways: true, + label: "Spent", + fontFamily: "sans-serif", + formatter: function (val) { + return totalSpentPercentage + "%"; + }, + }, + }, + }, + }, + }, + labels: projectArray, + legend: { + show: false, + }, + responsive: [ + { + breakpoint: 480, + options: { + chart: { + width: 200, + }, + legend: { + position: "bottom", + show: false, + }, + }, + }, + ], + }; + + const options: ApexOptions = { + chart: { + type: "bar", + height: 450, + }, + tooltip: { + enabled: true, // Enable tooltip + custom: ({ series, seriesIndex, dataPointIndex, w }) => { + + const projectCode = currentPageProjectList[dataPointIndex]; + const projectName = currentPageProjectNameList[dataPointIndex]; + const budgetManhours = currentPageProjectBudgetedManhourList[dataPointIndex]; + const spentManhours = currentPageProjectSpentManhourList[dataPointIndex]; + const value = series[seriesIndex][dataPointIndex]; + const tooltipContent = ` +
+ ${projectCode} - ${projectName} +
+ Budget Manhours: ${budgetManhours} hours +
+ Spent Manhours: ${spentManhours} hours +
+ Percentage: ${value}% +
+ `; + + return tooltipContent; + }, + }, + series: [{ + name: "Project Resource Consumption Percentage", + data: currentPagePercentage, + },], + colors: currentPageColor, + plotOptions: { + bar: { + horizontal: true, + distributed: true, + dataLabels: { + position: 'top' + }, + }, + }, + dataLabels: { + enabled: true, + textAnchor: 'end', + formatter: (val) => `${val}%`, + }, + xaxis: { + categories: currentPageProjectList, + }, + yaxis: { + title: { + text: "Projects", + }, + labels: { + maxWidth: 200, + style: { + cssClass: "apexcharts-yaxis-label", + }, + }, + }, + title: { + text: "Project Resource Consumption Percentage", + align: "center", + }, + grid: { + borderColor: "#f1f1f1", + xaxis: { + lines: { + show: true, + } + } + }, + annotations: {}, + }; + + const handleSearchSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = projectData.filter((row: any) => + newSelectionModel.includes(row.id), + ); + const teamIdList = [] + for (var i=0; i { + const selectedRowsData = teamProjectResult.filter((row:any) => + newSelectionModel.includes(row.id), + ); + console.log(selectedRowsData); + const projectArray = []; + const pieChartColorArray = []; + let totalSpent = 0; + let totalBudgetManhour = 0; + const percentageArray = []; + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length && i > 0) { + projectArray.push("Remained"); + } else if (selectedRowsData.length > 0) { + projectArray.push(selectedRowsData[i].projectName); + totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour); + totalSpent += Number(selectedRowsData[i].spentManhour); + pieChartColorArray.push(selectedRowsData[i].color); + } + } + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length && i > 0) { + const remainedManhour = totalBudgetManhour - totalSpent; + percentageArray.push( + Number(((remainedManhour / totalBudgetManhour) * 100).toFixed(1)), + ); + } else if (selectedRowsData.length > 0) { + const percentage = ( + (Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * + 100 + ).toFixed(1); + percentageArray.push(Number(percentage)); + } + } + setProjectBudgetManhour(totalBudgetManhour.toFixed(2)); + setActualManhourSpent(totalSpent.toFixed(2)); + setRemainedManhour((totalBudgetManhour - totalSpent).toFixed(2)); + setLastUpdate(new Date().toLocaleDateString("en-GB")); + setSelectionModel(newSelectionModel); + console.log(projectArray); + setProjectArray(projectArray); + setPercentageArray(percentageArray); + console.log(percentageArray); + setTotalSpentPercentage( + ((totalSpent / totalBudgetManhour) * 100).toFixed(1), + ); + if (projectArray.length > 0 && projectArray.includes("Remained")) { + const nonLastRecordColors = pieChartColorArray; + setColorArray([ + ...nonLastRecordColors.slice(0, projectArray.length - 1), + "#a3a3a3", + ]); + } else { + setColorArray(pieChartColorArray); + } + }; + + const startIndex = (currentPage - 1) * recordsPerPage; + const endIndex = startIndex + recordsPerPage; + useEffect(() => { + console.log(chartManhourConsumptionPercentage) + const currentPageProjectData = chartProjectName.slice(startIndex, endIndex) + const currentPageProjectName = chartProjectDisplayName.slice(startIndex, endIndex) + const currentPageProjectBudgetedManhour = chartProjectBudgetedHour.slice(startIndex, endIndex) + const currentPageProjectSpentManhour = chartProjectSpentHour.slice(startIndex, endIndex) + const currentPageData = chartManhourConsumptionPercentage.slice(startIndex, endIndex); + const colorArray = color.slice(startIndex, endIndex); + console.log(currentPage) + console.log(Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)) + setCurrentPageProjectList(currentPageProjectData) + setCurrentPageProjectNameList(currentPageProjectName) + setCurrentPageProjectBudgetedManhourList(currentPageProjectBudgetedManhour) + setCurrentPageProjectSpentManhourList(currentPageProjectSpentManhour) + setCurrentPagePercentage(currentPageData) + setCurrentPageColor(colorArray) + }, [chartManhourConsumptionPercentage,currentPage]); + + const handlePrevPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const handleNextPage = () => { + if (endIndex < chartManhourConsumptionPercentage.length) { + setCurrentPage(currentPage + 1); + } + }; + + const applySearch = (data: any) => { + console.log(data); + setSearchCriteria(data); + }; + return ( + <> + { + setFilteredResult( + projectData.filter( + (cp:any) => + cp.teamCode.toLowerCase().includes(query.teamCode.toLowerCase()) && + cp.teamName.toLowerCase().includes(query.teamName.toLowerCase()) + ), + ); + }} + /> + + +
+ + + +
+
+ + Sorting: + +
+
+ + + +
+ +
+ {currentPage === 1 && ( + + )} + {currentPage !== 1 && ( + + )} + {endIndex >= chartManhourConsumptionPercentage.length && ( + + )} + {endIndex < chartManhourConsumptionPercentage.length && ( + + )} + Page  + {chartManhourConsumptionPercentage.length === 0 && ( + 0 + )} + {chartManhourConsumptionPercentage.length > 0 && ( + currentPage + )} +  of  + {Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)} + +
+
+
+ + +
+ +
+
+
+
+
+ + + + {percentageArray.length === 0 && ( +
+ Please select the project you want to check. +
+ )} + {percentageArray.length > 0 && ( + + )} +
+
+ + +
+
+ Project Budget Manhour +
+
+ {projectBudgetManhour} +
+
+
+
+
+ Actual Manhour Spent +
+
+ {actualManhourSpent} +
+
+
+
+
+ Remained Manhour +
+
+ {remainedManhour} +
+
+
+
+
+ Last Update +
+
+ {lastUpdate} +
+
+
+
+
+
+ + ); +}; + +export default ProjectResourceConsumptionRanking; diff --git a/src/components/ProjectResourceConsumptionRanking/index.ts b/src/components/ProjectResourceConsumptionRanking/index.ts new file mode 100644 index 0000000..6259675 --- /dev/null +++ b/src/components/ProjectResourceConsumptionRanking/index.ts @@ -0,0 +1 @@ +export { default } from "./ProjectResourceConsumptionRanking"; diff --git a/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx b/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx index 5050323..7826bc3 100644 --- a/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx +++ b/src/components/ProjectResourceSummary/ProjectResourceSummary.tsx @@ -33,6 +33,7 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { useSearchParams } from 'next/navigation'; import {fetchResourceSummaryDetailResult} from "@/app/api/resourcesummary/actions"; +import { set } from "lodash"; const ProjectResourceSummary: React.FC = () => { @@ -43,6 +44,9 @@ const ProjectResourceSummary: React.FC = () => { const [selectionModel, setSelectionModel]: any[] = React.useState([]); const [projectName, setProjectName]:any = React.useState("NA"); const [projectFee, setProjectFee]:any = React.useState(0); + const [projectBudget, setProjectBudget]:any = React.useState(0); + const [expenditure, setExpenditure]:any = React.useState(0); + const [remainingBudget, setRemainingBudget]: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); @@ -94,6 +98,9 @@ const ProjectResourceSummary: React.FC = () => { const result = await fetchResourceSummaryDetailResult(intProjectId); setProjectName(result[0].summaryInformation[0].projectCodeAndName) setProjectFee(result[0].summaryInformation[0].totalFee) + setProjectBudget(result[0].summaryInformation[0].totalBudget) + setExpenditure(result[0].summaryInformation[0].expenditure) + setRemainingBudget(result[0].summaryInformation[0].remainingBudget) setStatus(result[0].summaryInformation[0].status) setPlannedResources(result[0].summaryInformation[0].plannedResources) setActualResourcesSpent(result[0].summaryInformation[0].resourcesSpent) @@ -233,8 +240,8 @@ const ProjectResourceSummary: React.FC = () => { return ( - *': { borderBottom: 'unset' } }}> - + *': { borderBottom: 'unset' } }} className="border-t-2 border-b-0 border-l-0 border-r-0 border-solid border-slate-300"> + {row.task.length > 0 && ( { {row.stage} {row.taskCount} - {row.g1Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g1Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g2Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g2Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g3Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g3Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g4Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g4Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g5Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.g5Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.totalPlanned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - {row.totalActual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g1Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g1Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g2Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g2Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g3Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g3Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g4Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g4Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g5Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.g5Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.totalPlanned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {row.totalActual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {row.task.map((taskRow:any) => ( - + @@ -307,7 +314,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[4].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[4].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -320,7 +327,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[5].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[5].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -333,7 +340,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[6].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[6].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -346,7 +353,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[7].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[7].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -359,7 +366,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[8].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[8].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -372,7 +379,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[9].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[9].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -385,7 +392,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[10].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[10].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -398,7 +405,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[11].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[11].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -411,7 +418,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[12].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[12].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -424,7 +431,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[13].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[13].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -437,7 +444,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[2].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[2].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -450,7 +457,7 @@ const ProjectResourceSummary: React.FC = () => { - {taskRow[3].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} + {taskRow[3].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
@@ -497,6 +504,9 @@ const columns2 = [ field: 'g1Planned', headerName: "Planned", flex: 0.7, + renderCell: (params: any) => { + return {params.row.g1Planned}; + }, }, { id: 'g1Actual', @@ -631,6 +641,36 @@ const columns2 = [ HKD ${projectFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+
+
+ + Total Budget + +
+
+ HKD ${projectBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+
+
+
+ + Cumculative Expenditure + +
+
+ HKD ${expenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+
+
+
+ + Remaining Budget + +
+
+ HKD ${remainingBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+
@@ -658,7 +698,7 @@ const columns2 = [
- {(actualResourcesSpent ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours + {(actualResourcesSpent ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours ({(actualResourcesSpent/plannedResources*100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%)
@@ -668,7 +708,7 @@ const columns2 = [
- {(remainingResources ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours + {(remainingResources ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours ({(remainingResources/plannedResources*100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%)
@@ -676,8 +716,11 @@ const columns2 = [
*/} {/*
*/} - - + + {/* */} + + +
@@ -702,22 +745,22 @@ const columns2 = [ Total - - - Stage - Task Count - Planned - Actual - Planned - Actual - Planned - Actual - Planned - Actual - Planned - Actual - Planned - Actual + + + Stage + Task Count + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual + Planned + Actual @@ -727,8 +770,6 @@ const columns2 = [
- {/*
*/} -
); }; diff --git a/src/components/StaffUtilization/StaffUtilization.tsx b/src/components/StaffUtilization/StaffUtilization.tsx index 431000a..6041d99 100644 --- a/src/components/StaffUtilization/StaffUtilization.tsx +++ b/src/components/StaffUtilization/StaffUtilization.tsx @@ -34,6 +34,7 @@ import moment from "moment"; import { fetchTeamCombo, fetchweeklyTeamTotalManhours, fetchmonthlyTeamTotalManhours, fetchTotalManhoursByGrade, fetchWeeklyUnsubmit, fetchMonthlyUnsubmit, fetchStaffCombo, fetchDailyIndividualStaffManhours, fetchWeeklyIndividualStaffManhours, fetchMonthlyIndividualStaffManhours } from "@/app/api/staffUtilization"; import { SessionStaff } from "@/config/authConfig"; import { VIEW_DASHBOARD_ALL } from "@/middleware"; +import { QrCode } from "@mui/icons-material"; dayjs.extend(isBetweenPlugin); interface CustomPickerDayProps extends PickersDayProps { @@ -137,15 +138,15 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { firstDayOfWeekString + " to " + lastDayOfWeekString, ); const [unsubmittedTimeSheetSelect, setUnsubmittedTimeSheetSelect]: any = - React.useState("Weekly"); + React.useState("Monthly"); const [teamTotalManhoursSpentSelect, setTeamTotalManhoursSpentSelect]: any = - React.useState("Weekly"); + React.useState("Monthly"); const [staffGradeManhoursSpentSelect, setStaffGradeManhoursSpentSelect]: any = - React.useState("Weekly"); + React.useState("Monthly"); const [ individualStaffManhoursSpentSelect, setIndividualStaffManhoursSpentSelect, - ]: any = React.useState("Daily"); + ]: any = React.useState("Monthly"); const weekDates: any[] = []; const monthDates: any[] = []; const currentDate = dayjs(); @@ -665,13 +666,13 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { if (staffGradeManhoursSpentSelect === "Weekly") { fetchTotalManhoursByGradeData() } - }, [weeklyValueByStaffGrade, weeklyToValueByStaffGrade]); + }, [staffGradeTeamId, weeklyValueByStaffGrade, weeklyToValueByStaffGrade]); useEffect(() => { if (staffGradeManhoursSpentSelect === "Monthly") { fetchMonthlyTotalManhoursByGradeData() } - }, [totalManHoursMonthlyFromValue, totalManHoursMonthlyToValue]); + }, [staffGradeTeamId, totalManHoursMonthlyFromValue, totalManHoursMonthlyToValue]); useEffect(() => { if (unsubmittedTimeSheetSelect === "Weekly") { @@ -703,6 +704,21 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { } }, [staffId, indivdualManHoursMonthlyFromValue]); + // useEffect(() => { + // console.log(unsubmittedTimeSheetSelect) + // if (unsubmittedTimeSheetSelect === "Monthly" || teamTotalManhoursSpentSelect === "Monthly" || staffGradeManhoursSpentSelect === "Monthly" || individualStaffManhoursSpentSelect === "Monthly") { + // setUnsubmittedTimeSheetSelect("Monthly") + // setTeamTotalManhoursSpentSelect("Monthly") + // setStaffGradeManhoursSpentSelect("Monthly") + // setIndividualStaffManhoursSpentSelect("Monthly") + // } else if (unsubmittedTimeSheetSelect === "Weekly" || teamTotalManhoursSpentSelect === "Weekly" || staffGradeManhoursSpentSelect === "Weekly" || individualStaffManhoursSpentSelect === "Weekly") { + // setUnsubmittedTimeSheetSelect("Weekly") + // setTeamTotalManhoursSpentSelect("Weekly") + // setStaffGradeManhoursSpentSelect("Weekly") + // setIndividualStaffManhoursSpentSelect("Weekly") + // } + // }, [unsubmittedTimeSheetSelect, teamTotalManhoursSpentSelect, staffGradeManhoursSpentSelect, individualStaffManhoursSpentSelect]); + @@ -828,7 +844,7 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { const staffGradeOptions: ApexOptions = { chart: { height: 350, - type: "line", + type: "bar", }, stroke: { width: [2, 2], @@ -972,6 +988,7 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { const teamTotalManhoursSpentOnClick = (r: any) => { setTeamTotalManhoursSpentSelect(r); + if (r === "Weekly") { fetchWeeklyTeamManhourSpentData() setValue(dayjs().startOf('week')); @@ -1321,8 +1338,15 @@ const StaffUtilization: React.FC = ({ abilities, staff }) => { Weekly + */} + */}