| @@ -15,72 +15,10 @@ export interface ClientProjectResult { | |||
| projectNo: number; | |||
| } | |||
| // export interface ClientSubsidiaryProjectResult { | |||
| // projectId: number; | |||
| // projectCode: string; | |||
| // projectName: string; | |||
| // team: string; | |||
| // teamLead: string; | |||
| // expectedStage: string; | |||
| // budgetedManhour: number; | |||
| // spentManhour: number; | |||
| // remainedManhour: number; | |||
| // manhourConsumptionPercentage: number; | |||
| // comingPaymentMilestone: string; | |||
| // } | |||
| export const preloadClientProjects = () => { | |||
| fetchAllClientProjects(); | |||
| }; | |||
| // export const fetchClientProjects = cache(async () => { | |||
| // return mockProjects; | |||
| // }); | |||
| export const fetchAllClientProjects = cache(async () => { | |||
| return serverFetchJson<ClientProjectResult[]>(`${BASE_API_URL}/dashboard/searchCustomerSubsidiary`); | |||
| }); | |||
| // export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, subsidiaryId: number) => { | |||
| // return serverFetchJson<ClientSubsidiaryProjectResult>( | |||
| // `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject/?${customerId}&${subsidiaryId}`, | |||
| // { | |||
| // next: { tags: [`allClientSubsidiaryProjects`] }, | |||
| // }, | |||
| // ); | |||
| // }); | |||
| // const mockProjects: ClientProjectResult[] = [ | |||
| // { | |||
| // id: 1, | |||
| // clientCode: "CUST-001", | |||
| // clientName: "Client A", | |||
| // SubsidiaryClientCode: "N/A", | |||
| // SubsidiaryClientName: "N/A", | |||
| // NoOfProjects: 5, | |||
| // }, | |||
| // { | |||
| // id: 2, | |||
| // clientCode: "CUST-001", | |||
| // clientName: "Client A", | |||
| // SubsidiaryClientCode: "SUBS-001", | |||
| // SubsidiaryClientName: "Subsidiary A", | |||
| // NoOfProjects: 5, | |||
| // }, | |||
| // { | |||
| // id: 3, | |||
| // clientCode: "CUST-001", | |||
| // clientName: "Client A", | |||
| // SubsidiaryClientCode: "SUBS-002", | |||
| // SubsidiaryClientName: "Subsidiary B", | |||
| // NoOfProjects: 3, | |||
| // }, | |||
| // { | |||
| // id: 4, | |||
| // clientCode: "CUST-001", | |||
| // clientName: "Client A", | |||
| // SubsidiaryClientCode: "SUBS-003", | |||
| // SubsidiaryClientName: "Subsidiary C", | |||
| // NoOfProjects: 1, | |||
| // }, | |||
| // ]; | |||
| @@ -0,0 +1,54 @@ | |||
| "use server"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Dayjs } from "dayjs"; | |||
| import { cache } from "react"; | |||
| export interface FinancialSummaryByClientResult { | |||
| teamId:number; | |||
| id:number; | |||
| customerCode: string; | |||
| customerName: string; | |||
| projectNo: number; | |||
| totalFee: number; | |||
| totalBudget: number; | |||
| cumulativeExpenditure: number; | |||
| totalInvoiced: number; | |||
| totalReceived: number; | |||
| cashFlowStatus: string; | |||
| cpi: number; | |||
| totalUninvoiced: number; | |||
| } | |||
| export interface FinancialSummaryByProjectResult { | |||
| teamId:number; | |||
| id:number; | |||
| projectCode: string; | |||
| projectName: string; | |||
| customerName: string; | |||
| projectNo: number; | |||
| totalFee: number; | |||
| totalBudget: number; | |||
| cumulativeExpenditure: number; | |||
| totalInvoiced: number; | |||
| totalReceived: number; | |||
| cashFlowStatus: string; | |||
| cpi: number; | |||
| totalUninvoiced: number; | |||
| } | |||
| export const searchFinancialSummaryByClient = cache(async (teamId: number) => { | |||
| return serverFetchJson<FinancialSummaryByClientResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchFinancialSummaryByClient?teamId=${teamId}` | |||
| ); | |||
| }); | |||
| export const searchFinancialSummaryByProject = cache(async (teamId: number) => { | |||
| return serverFetchJson<FinancialSummaryByProjectResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchFinancialSummaryByProject?teamId=${teamId}` | |||
| ); | |||
| }); | |||
| @@ -0,0 +1,27 @@ | |||
| "use server"; | |||
| import { cache } from "react"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| // import "server-only"; | |||
| export interface FinancialSummaryCardResult { | |||
| teamId: number; | |||
| teamName: string; | |||
| projectNo: number; | |||
| totalFee: number; | |||
| totalBudget: number; | |||
| cumulativeExpenditure: number; | |||
| totalInvoiced: number; | |||
| totalReceived: number; | |||
| cashFlowStatus: string; | |||
| cpi: number; | |||
| } | |||
| export const preloadFinancialSummaryCard = () => { | |||
| fetchFinancialSummaryCard(); | |||
| }; | |||
| export const fetchFinancialSummaryCard = cache(async () => { | |||
| return serverFetchJson<FinancialSummaryCardResult[]>(`${BASE_API_URL}/dashboard/searchFinancialSummaryCard`); | |||
| }); | |||
| @@ -0,0 +1,29 @@ | |||
| "use server"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Dayjs } from "dayjs"; | |||
| import { cache } from "react"; | |||
| export interface ClientSubsidiaryProjectResult { | |||
| color: string; | |||
| projectId: number; | |||
| projectCode: string; | |||
| projectName: string; | |||
| team: string; | |||
| teamLead: string; | |||
| expectedStage: string; | |||
| budgetedManhour: number; | |||
| spentManhour: number; | |||
| remainedManhour: number; | |||
| manhourConsumptionPercentage: number; | |||
| comingPaymentMilestone: string; | |||
| } | |||
| export const fetchAllTeamProjects = cache(async (teamLeadId: number) => { | |||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchTeamProject?teamLeadId=${teamLeadId}` | |||
| ); | |||
| }); | |||
| @@ -1,10 +1,15 @@ | |||
| import { cache } from "react"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import "server-only"; | |||
| export interface TeamProjectResult { | |||
| id: number; | |||
| teamId: number; | |||
| teamLeadId: number; | |||
| teamCode: string; | |||
| teamName: string; | |||
| NoOfProjects: number; | |||
| projectNo: number; | |||
| } | |||
| export const preloadProjects = () => { | |||
| @@ -12,32 +17,6 @@ export const preloadProjects = () => { | |||
| }; | |||
| export const fetchTeamProjects = cache(async () => { | |||
| return mockProjects; | |||
| return serverFetchJson<TeamProjectResult[]>(`${BASE_API_URL}/dashboard/searchTeamProjectNo`); | |||
| }); | |||
| const mockProjects: TeamProjectResult[] = [ | |||
| { | |||
| id: 1, | |||
| teamCode: "TEAM-001", | |||
| teamName: "Team A", | |||
| NoOfProjects: 5, | |||
| }, | |||
| { | |||
| id: 2, | |||
| teamCode: "TEAM-002", | |||
| teamName: "Team B", | |||
| NoOfProjects: 5, | |||
| }, | |||
| { | |||
| id: 3, | |||
| teamCode: "TEAM-003", | |||
| teamName: "Team C", | |||
| NoOfProjects: 3, | |||
| }, | |||
| { | |||
| id: 4, | |||
| teamCode: "TEAM-004", | |||
| teamName: "Team D", | |||
| NoOfProjects: 1, | |||
| }, | |||
| ]; | |||
| @@ -303,130 +303,6 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| flex:0.1 | |||
| }, | |||
| ]; | |||
| const optionstest: ApexOptions = { | |||
| chart: { | |||
| height: 350, | |||
| type: "line", | |||
| }, | |||
| stroke: { | |||
| width: [0, 0, 2, 2], | |||
| }, | |||
| plotOptions: { | |||
| bar: { | |||
| horizontal: false, | |||
| distributed: false, | |||
| }, | |||
| }, | |||
| dataLabels: { | |||
| enabled: false, | |||
| }, | |||
| xaxis: { | |||
| categories: [ | |||
| "Q1", | |||
| "Q2", | |||
| "Q3", | |||
| "Q4", | |||
| "Q5", | |||
| "Q6", | |||
| "Q7", | |||
| "Q8", | |||
| "Q9", | |||
| "Q10", | |||
| "Q11", | |||
| "Q12", | |||
| ], | |||
| }, | |||
| yaxis: [ | |||
| { | |||
| title: { | |||
| text: "Monthly Income and Expenditure(HKD)", | |||
| }, | |||
| min: 0, | |||
| max: 350000, | |||
| tickAmount: 5, | |||
| labels: { | |||
| formatter: function (val) { | |||
| return val.toLocaleString() | |||
| } | |||
| } | |||
| }, | |||
| { | |||
| show: false, | |||
| seriesName: "Monthly_Expenditure", | |||
| title: { | |||
| text: "Monthly Expenditure (HKD)", | |||
| }, | |||
| min: 0, | |||
| max: 350000, | |||
| tickAmount: 5, | |||
| }, | |||
| { | |||
| seriesName: "Cumulative_Income", | |||
| opposite: true, | |||
| title: { | |||
| text: "Cumulative Income and Expenditure(HKD)", | |||
| }, | |||
| min: 0, | |||
| max: 850000, | |||
| tickAmount: 5, | |||
| labels: { | |||
| formatter: function (val) { | |||
| return val.toLocaleString() | |||
| } | |||
| } | |||
| }, | |||
| { | |||
| show: false, | |||
| seriesName: "Cumulative_Expenditure", | |||
| opposite: true, | |||
| title: { | |||
| text: "Cumulative Expenditure (HKD)", | |||
| }, | |||
| min: 0, | |||
| max: 850000, | |||
| tickAmount: 5, | |||
| }, | |||
| ], | |||
| grid: { | |||
| borderColor: "#f1f1f1", | |||
| }, | |||
| annotations: {}, | |||
| series: [ | |||
| { | |||
| name: "Monthly_Income", | |||
| type: "column", | |||
| color: "#ffde91", | |||
| data: [0, 110000, 0, 0, 185000, 0, 0, 189000, 0, 0, 300000, 0], | |||
| }, | |||
| { | |||
| name: "Monthly_Expenditure", | |||
| type: "column", | |||
| color: "#82b59a", | |||
| data: [ | |||
| 0, 160000, 120000, 120000, 55000, 55000, 55000, 55000, 55000, 70000, | |||
| 55000, 55000, | |||
| ], | |||
| }, | |||
| { | |||
| name: "Cumulative_Income", | |||
| type: "line", | |||
| color: "#EE6D7A", | |||
| data: [ | |||
| 0, 100000, 100000, 100000, 300000, 300000, 300000, 500000, 500000, | |||
| 500000, 800000, 800000, | |||
| ], | |||
| }, | |||
| { | |||
| name: "Cumulative_Expenditure", | |||
| type: "line", | |||
| color: "#7cd3f2", | |||
| data: [ | |||
| 0, 198000, 240000, 400000, 410000, 430000, 510000, 580000, 600000, | |||
| 710000, 730000, 790000, | |||
| ], | |||
| }, | |||
| ], | |||
| }; | |||
| const options2: ApexOptions = { | |||
| chart: { | |||
| @@ -18,9 +18,13 @@ 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} from "@/app/api/teamprojects/actions"; | |||
| // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | |||
| const ProgressByTeam: React.FC = () => { | |||
| const searchParams = useSearchParams(); | |||
| const teamLeadId = searchParams.get('teamLeadId'); | |||
| const [activeTab, setActiveTab] = useState("financialSummary"); | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| const { t } = useTranslation("dashboard"); | |||
| @@ -44,6 +48,64 @@ const ProgressByTeam: React.FC = () => { | |||
| const [receiptFromDate, setReceiptFromDate] = useState(null); | |||
| const [receiptToDate, setReceiptToDate] = useState(null); | |||
| const [selectedRows, setSelectedRows] = useState([]); | |||
| const [chartProjectName, setChartProjectName]: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 fetchData = async () => { | |||
| if (teamLeadId) { | |||
| try { | |||
| const clickResult = await fetchAllTeamProjects( | |||
| Number(teamLeadId)) | |||
| console.log(clickResult) | |||
| setTeamProjectResult(clickResult); | |||
| } catch (error) { | |||
| console.error('Error fetching team projects:', error); | |||
| } | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| const projectName = [] | |||
| const manhourConsumptionPercentage = [] | |||
| for (let i = 0; i < teamProjectResult.length; i++){ | |||
| teamProjectResult[i].color = color[i] | |||
| projectName.push(teamProjectResult[i].projectName) | |||
| manhourConsumptionPercentage.push(teamProjectResult[i].manhourConsumptionPercentage) | |||
| } | |||
| setChartProjectName(projectName) | |||
| setChartManhourConsumptionPercentage(manhourConsumptionPercentage) | |||
| }, [teamProjectResult]); | |||
| useEffect(() => { | |||
| fetchData() | |||
| }, [teamLeadId]); | |||
| const rows = [ | |||
| { | |||
| id: 1, | |||
| @@ -373,7 +435,35 @@ const ProgressByTeam: React.FC = () => { | |||
| type: "bar", | |||
| height: 350, | |||
| }, | |||
| colors: ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b"], | |||
| series: [{ | |||
| name: "Project Resource Consumption Percentage", | |||
| data: chartManhourConsumptionPercentage, | |||
| },], | |||
| colors: ["#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"], | |||
| plotOptions: { | |||
| bar: { | |||
| horizontal: true, | |||
| @@ -384,13 +474,7 @@ const ProgressByTeam: React.FC = () => { | |||
| enabled: false, | |||
| }, | |||
| xaxis: { | |||
| categories: [ | |||
| "Consultancy Project 123", | |||
| "Consultancy Project 456", | |||
| "Construction Project A", | |||
| "Construction Project B", | |||
| "Construction Project C", | |||
| ], | |||
| categories: chartProjectName, | |||
| }, | |||
| yaxis: { | |||
| title: { | |||
| @@ -414,7 +498,7 @@ const ProgressByTeam: React.FC = () => { | |||
| }; | |||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||
| const selectedRowsData = rows2.filter((row) => | |||
| const selectedRowsData = teamProjectResult.filter((row:any) => | |||
| newSelectionModel.includes(row.id), | |||
| ); | |||
| console.log(selectedRowsData); | |||
| @@ -427,7 +511,7 @@ const ProgressByTeam: React.FC = () => { | |||
| if (i === selectedRowsData.length && i > 0) { | |||
| projectArray.push("Remained"); | |||
| } else if (selectedRowsData.length > 0) { | |||
| projectArray.push(selectedRowsData[i].project); | |||
| projectArray.push(selectedRowsData[i].projectName); | |||
| totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour); | |||
| totalSpent += Number(selectedRowsData[i].spentManhour); | |||
| pieChartColorArray.push(selectedRowsData[i].color); | |||
| @@ -485,7 +569,7 @@ const ProgressByTeam: React.FC = () => { | |||
| <div style={{ display: "inline-block", width: "99%" }}> | |||
| <ReactApexChart | |||
| options={options} | |||
| series={series} | |||
| series={options.series} | |||
| type="bar" | |||
| height={350} | |||
| /> | |||
| @@ -507,7 +591,7 @@ const ProgressByTeam: React.FC = () => { | |||
| style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | |||
| > | |||
| <CustomDatagrid | |||
| rows={rows2} | |||
| rows={teamProjectResult} | |||
| columns={columns2} | |||
| columnWidth={200} | |||
| dataGridHeight={300} | |||
| @@ -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 { TeamProjectResult } from "@/app/api/teamprojects"; | |||
| import VisibilityIcon from '@mui/icons-material/Visibility'; | |||
| import { useRouter, useSearchParams } from "next/navigation"; | |||
| interface Props { | |||
| projects: TeamProjectResult[]; | |||
| @@ -15,7 +17,7 @@ type SearchParamNames = keyof SearchQuery; | |||
| const ProgressByClientSearch: React.FC<Props> = ({ projects }) => { | |||
| const { t } = useTranslation("projects"); | |||
| const router = useRouter(); | |||
| // If project searching is done on the server-side, then no need for this. | |||
| const [filteredProjects, setFilteredProjects] = useState(projects); | |||
| @@ -27,13 +29,31 @@ const ProgressByClientSearch: React.FC<Props> = ({ projects }) => { | |||
| [t], | |||
| ); | |||
| const onTaskClick = useCallback(async (teamProjectResult: TeamProjectResult) => { | |||
| try { | |||
| console.log(teamProjectResult) | |||
| router.push( | |||
| `/dashboard/ProjectStatusByTeam?teamLeadId=${teamProjectResult.teamLeadId}` | |||
| ); | |||
| } catch (error) { | |||
| console.error('Error fetching team projects:', error); | |||
| } | |||
| }, []); | |||
| const columns = useMemo<Column<TeamProjectResult>[]>( | |||
| () => [ | |||
| { | |||
| name: "id", | |||
| label: t("Details"), | |||
| onClick: onTaskClick, | |||
| buttonIcon: <VisibilityIcon />, | |||
| }, | |||
| { name: "teamCode", label: t("Team Code") }, | |||
| { name: "teamName", label: t("Team Name") }, | |||
| { name: "NoOfProjects", label: t("No. of Projects") }, | |||
| { name: "projectNo", label: t("No. of Projects") }, | |||
| ], | |||
| [t], | |||
| [onTaskClick, t], | |||
| ); | |||
| return ( | |||
| @@ -41,7 +61,13 @@ const ProgressByClientSearch: React.FC<Props> = ({ projects }) => { | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| console.log(query); | |||
| setFilteredProjects( | |||
| projects.filter( | |||
| (cp) => | |||
| cp.teamCode.toLowerCase().includes(query.teamCode.toLowerCase()) && | |||
| cp.teamName.toLowerCase().includes(query.teamName.toLowerCase()) | |||
| ), | |||
| ); | |||
| }} | |||
| /> | |||
| <SearchResults<TeamProjectResult> | |||
| @@ -20,14 +20,14 @@ import { Suspense } from "react"; | |||
| interface Props { | |||
| Title: string; | |||
| TotalActiveProjectNumber: string; | |||
| TotalFees: string; | |||
| TotalBudget: string; | |||
| TotalCumulative: string; | |||
| TotalInvoicedAmount: string; | |||
| TotalReceivedAmount: string; | |||
| TotalActiveProjectNumber: number; | |||
| TotalFees: number; | |||
| TotalBudget: number; | |||
| TotalCumulative: number; | |||
| TotalInvoicedAmount: number; | |||
| TotalReceivedAmount: number; | |||
| CashFlowStatus: string; | |||
| CostPerformanceIndex: string; | |||
| CostPerformanceIndex: number; | |||
| ClickedIndex: number; | |||
| Index: number; | |||
| } | |||
| @@ -77,42 +77,42 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| Total Active Project | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalActiveProjectNumber} | |||
| {TotalActiveProjectNumber.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Fees | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalFees} | |||
| {TotalFees.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Budget | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalBudget} | |||
| {TotalBudget.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Cumulative Expenditure | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalCumulative} | |||
| {TotalCumulative.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Invoiced Amount | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalInvoicedAmount} | |||
| {TotalInvoicedAmount.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Received Amount | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalReceivedAmount} | |||
| {TotalReceivedAmount.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| @@ -18,86 +18,32 @@ import { AnyARecord, AnyCnameRecord } from "dns"; | |||
| import SearchBox, { Criterion } from "../SearchBox"; | |||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||
| import { Suspense } from "react"; | |||
| import { fetchFinancialSummaryCard } from "@/app/api/financialsummary"; | |||
| import { searchFinancialSummaryByClient,searchFinancialSummaryByProject } from "@/app/api/financialsummary/actions"; | |||
| import ProjectFinancialCard from "./ProjectFinancialCard"; | |||
| const ProjectFinancialSummary: React.FC = () => { | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| const { t } = useTranslation("dashboard"); | |||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
| const projectFinancialData = [ | |||
| { | |||
| id: 1, | |||
| title: "All Teams", | |||
| activeProject: "147", | |||
| fees: "22,800,000.00", | |||
| budget: "18,240,000.00", | |||
| cumulativeExpenditure: "17,950,000.00", | |||
| invoicedAmount: "18,240,000.00", | |||
| receivedAmount: "10,900,000.00", | |||
| cashFlowStatus: "Negative", | |||
| CPI: "0.69", | |||
| }, | |||
| { | |||
| id: 2, | |||
| title: "XXX Team", | |||
| activeProject: "25", | |||
| fees: "1,500,000.00", | |||
| budget: "1,200,000.00", | |||
| cumulativeExpenditure: "1,250,000.00", | |||
| invoicedAmount: "900,000.00", | |||
| receivedAmount: "650,000.00", | |||
| cashFlowStatus: "Negative", | |||
| CPI: "0.72", | |||
| }, | |||
| { | |||
| id: 3, | |||
| title: "YYY Team", | |||
| activeProject: "35", | |||
| fees: "5,000,000.00", | |||
| budget: "4,000,000.00", | |||
| cumulativeExpenditure: "3,200,000.00", | |||
| invoicedAmount: "3,500,000.00", | |||
| receivedAmount: "3,500,000.00", | |||
| cashFlowStatus: "Positive", | |||
| CPI: "1.09", | |||
| }, | |||
| { | |||
| id: 4, | |||
| title: "ZZZ Team", | |||
| activeProject: "50", | |||
| fees: "3,500,000.00", | |||
| budget: "2,800,000.00", | |||
| cumulativeExpenditure: "5,600,000.00", | |||
| invoicedAmount: "2,500,000.00", | |||
| receivedAmount: "2,200,000.00", | |||
| cashFlowStatus: "Negative", | |||
| CPI: "0.45", | |||
| }, | |||
| { | |||
| id: 5, | |||
| title: "AAA Team", | |||
| activeProject: "15", | |||
| fees: "4,800,000.00", | |||
| budget: "3,840,000.00", | |||
| cumulativeExpenditure: "2,500,000.00", | |||
| invoicedAmount: "1,500,000.00", | |||
| receivedAmount: "750,000.00", | |||
| cashFlowStatus: "Negative", | |||
| CPI: "0.60", | |||
| }, | |||
| { | |||
| id: 6, | |||
| title: "BBB Team", | |||
| activeProject: "22", | |||
| fees: "8,000,000.00", | |||
| budget: "6,400,000.00", | |||
| cumulativeExpenditure: "5,400,000.00", | |||
| invoicedAmount: "4,000,000.00", | |||
| receivedAmount: "3,800,000.00", | |||
| cashFlowStatus: "Negative", | |||
| CPI: "0.74", | |||
| }, | |||
| ]; | |||
| const [projectFinancialData, setProjectFinancialData]: any[] = React.useState([]); | |||
| const [clientFinancialRows, setClientFinancialRows]: any[] = React.useState([]); | |||
| const [projectFinancialRows, setProjectFinancialRows]: any[] = React.useState([]); | |||
| const fetchData = async () => { | |||
| const financialSummaryCard = await fetchFinancialSummaryCard(); | |||
| setProjectFinancialData(financialSummaryCard) | |||
| } | |||
| const fetchTableData = async (teamId:any) => { | |||
| const financialSummaryByClient = await searchFinancialSummaryByClient(teamId); | |||
| const financialSummaryByProject = await searchFinancialSummaryByProject(teamId); | |||
| console.log(financialSummaryByClient) | |||
| console.log(financialSummaryByProject) | |||
| setClientFinancialRows(financialSummaryByClient) | |||
| setProjectFinancialRows(financialSummaryByProject) | |||
| } | |||
| useEffect(() => { | |||
| fetchData() | |||
| }, []); | |||
| const rows0 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, | |||
| {id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"}, | |||
| @@ -115,45 +61,46 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| {id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} | |||
| ] | |||
| const projectFinancialRows = [{id: 1,projectCode:"M1354",projectName:"Consultanct Project BBB",clientName:"Client D",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||
| ] | |||
| // const projectFinancialRows = [{id: 1,projectCode:"M1354",projectName:"Consultanct Project BBB",clientName:"Client D",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||
| // ] | |||
| const clientFinancialRows =[{id: 1,clientCode:"Cust-02",clientName:"Client B",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||
| {id: 2,clientCode:"Cust-03",clientName:"Client C",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||
| {id: 3,clientCode:"Cust-04",clientName:"Client D",totalProjectInvolved:"4",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||
| ] | |||
| // const clientFinancialRows =[{id: 1,clientCode:"Cust-02",clientName:"Client B",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||
| // {id: 2,clientCode:"Cust-03",clientName:"Client C",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||
| // {id: 3,clientCode:"Cust-04",clientName:"Client D",totalProjectInvolved:"4",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||
| // ] | |||
| const [isCardClickedIndex, setIsCardClickedIndex] = React.useState(0); | |||
| const [selectedTeamData, setSelectedTeamData]: any[] = React.useState(rows0); | |||
| const handleCardClick = (r: any) => { | |||
| setIsCardClickedIndex(r); | |||
| if (r === 0) { | |||
| setSelectedTeamData(rows0); | |||
| } else if (r === 1) { | |||
| setSelectedTeamData(rows1); | |||
| } else if (r === 2) { | |||
| setSelectedTeamData(rows2); | |||
| } | |||
| fetchTableData(r.teamId) | |||
| // setIsCardClickedIndex(r); | |||
| // if (r === 0) { | |||
| // setSelectedTeamData(rows0); | |||
| // } else if (r === 1) { | |||
| // setSelectedTeamData(rows1); | |||
| // } else if (r === 2) { | |||
| // setSelectedTeamData(rows2); | |||
| // } | |||
| }; | |||
| const columns = [ | |||
| { | |||
| id: 'clientCode', | |||
| field: 'clientCode', | |||
| id: 'customerCode', | |||
| field: 'customerCode', | |||
| headerName: "Client Code", | |||
| flex: 0.7, | |||
| }, | |||
| { | |||
| id: 'clientName', | |||
| field: 'clientName', | |||
| id: 'customerName', | |||
| field: 'customerName', | |||
| headerName: "Client Name", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'totalProjectInvolved', | |||
| field: 'totalProjectInvolved', | |||
| id: 'projectNo', | |||
| field: 'projectNo', | |||
| headerName: "Total Project Involved", | |||
| flex: 1, | |||
| }, | |||
| @@ -163,6 +110,7 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| headerName: "Cash Flow Status", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| console.log(params.row) | |||
| if (params.row.cashFlowStatus === "Positive") { | |||
| return ( | |||
| <span className="text-lime-500">{params.row.cashFlowStatus}</span> | |||
| @@ -192,13 +140,13 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalFees', | |||
| field: 'totalFees', | |||
| id: 'totalFee', | |||
| field: 'totalFee', | |||
| headerName: "Total Fees (HKD)", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalFees}</span> | |||
| <span>${params.row.totalFee}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -214,46 +162,46 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalCumulativeExpenditure', | |||
| field: 'totalCumulativeExpenditure', | |||
| id: 'cumulativeExpenditure', | |||
| field: 'cumulativeExpenditure', | |||
| headerName: "Total Cumulative Expenditure (HKD)", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalCumulativeExpenditure}</span> | |||
| <span>${params.row.cumulativeExpenditure}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalInvoicedAmount', | |||
| field: 'totalInvoicedAmount', | |||
| id: 'totalInvoiced', | |||
| field: 'totalInvoiced', | |||
| headerName: "Total Invoiced Amount (HKD)", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalInvoicedAmount}</span> | |||
| <span>${params.row.totalInvoiced}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalUnInvoicedAmount', | |||
| field: 'totalUnInvoicedAmount', | |||
| id: 'totalUnInvoiced', | |||
| field: 'totalUnInvoiced', | |||
| headerName: "Total Un-invoiced Amount (HKD)", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalUnInvoicedAmount}</span> | |||
| <span>${params.row.totalUninvoiced}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalReceivedAmount', | |||
| field: 'totalReceivedAmount', | |||
| id: 'totalReceived', | |||
| field: 'totalReceived', | |||
| headerName: "Total Received Amount (HKD)", | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalReceivedAmount}</span> | |||
| <span>${params.row.totalReceived}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -322,8 +270,8 @@ const columns2 = [ | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'clientName', | |||
| field: 'clientName', | |||
| id: 'customerName', | |||
| field: 'customerName', | |||
| headerName: "Client Name", | |||
| flex: 1, | |||
| }, | |||
| @@ -365,7 +313,7 @@ const columns2 = [ | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalFees}</span> | |||
| <span>${params.row.totalFee}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -387,7 +335,7 @@ const columns2 = [ | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalCumulativeExpenditure}</span> | |||
| <span>${params.row.cumulativeExpenditure}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -398,7 +346,7 @@ const columns2 = [ | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalInvoicedAmount}</span> | |||
| <span>${params.row.totalInvoiced}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -409,7 +357,7 @@ const columns2 = [ | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalUnInvoicedAmount}</span> | |||
| <span>${params.row.totalUninvoiced}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -420,7 +368,7 @@ const columns2 = [ | |||
| flex: 1, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalReceivedAmount}</span> | |||
| <span>${params.row.totalReceived}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| @@ -438,9 +386,9 @@ const columns2 = [ | |||
| <Card> | |||
| <CardHeader className="text-slate-500" title="Active Project Financial Status"/> | |||
| <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | |||
| {projectFinancialData.map((record, index) => ( | |||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(index)}> | |||
| <ProjectFinancialCard Title={record.title} TotalActiveProjectNumber={record.activeProject} TotalFees={record.fees} TotalBudget={record.budget} TotalCumulative={record.cumulativeExpenditure} TotalInvoicedAmount={record.invoicedAmount} TotalReceivedAmount={record.receivedAmount} CashFlowStatus={record.cashFlowStatus} CostPerformanceIndex={record.CPI} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
| {projectFinancialData.map((record:any, index:any) => ( | |||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record)}> | |||
| <ProjectFinancialCard Title={record.teamName} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure} TotalInvoicedAmount={record.totalInvoiced} TotalReceivedAmount={record.totalReceived} CashFlowStatus={record.cashFlowStatus} CostPerformanceIndex={record.cpi} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
| </div> | |||
| ))} | |||
| </div> | |||