From eecf73aad9f265cbb2d94e9b214fd6538ecab253 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 21 Jan 2025 16:59:20 +0800 Subject: [PATCH 01/11] update financial summary V2 --- src/app/api/financialsummary/actions.ts | 29 ++- .../FinnancialStatusByProject.tsx | 169 +++++++++++------- 2 files changed, 135 insertions(+), 63 deletions(-) diff --git a/src/app/api/financialsummary/actions.ts b/src/app/api/financialsummary/actions.ts index 0ada047..e8a6439 100644 --- a/src/app/api/financialsummary/actions.ts +++ b/src/app/api/financialsummary/actions.ts @@ -6,7 +6,8 @@ import { Dayjs } from "dayjs"; import { cache } from "react"; import { FileResponse } from "../reports/actions"; import { revalidateTag } from "next/cache"; - +import { SumOfByClient } from "@/components/ProjectFinancialSummaryV2/gptFn"; +import { FinancialByProject } from "."; export interface FinancialSummaryByClientResult { teamId:number; @@ -144,6 +145,32 @@ export const exportFinancialSummaryByProjectExcel = cache(async (data: ExportFin return reportBlob }) +export const exportFinancialSummaryV2ByClientExcel = cache(async (data: SumOfByClient[]) => { + const reportBlob = await serverFetchBlob( + `${BASE_API_URL}/dashboard/exportFinancialSummaryV2ByClientExcel`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, +); + + return reportBlob +}) + +export const exportFinancialSummaryV2ByProjectExcel = cache(async (data: FinancialByProject[]) => { + const reportBlob = await serverFetchBlob( + `${BASE_API_URL}/dashboard/exportFinancialSummaryV2ByProjectExcel`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, +); + + return reportBlob +}) + export const revalidate = async(tag: string) => { revalidateTag(tag) } diff --git a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx index fe02544..63f53bb 100644 --- a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx @@ -8,8 +8,10 @@ import { useEffect, useMemo, useState } from "react"; import CustomDatagrid from "../CustomDatagrid"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/navigation"; -import { Box } from "@mui/material"; +import { Box, Card, CardHeader } from "@mui/material"; import { SumOfByClient } from "./gptFn"; +import { exportFinancialSummaryV2ByClientExcel, exportFinancialSummaryV2ByProjectExcel } from "@/app/api/financialsummary/actions"; +import { downloadFile } from "@/app/utils/commonUtil"; // import { summarizeFinancialData } from "./gptFn"; interface Props { @@ -99,7 +101,8 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Cash Flow Status"), minWidth: 80, renderCell: (params: any) => { - if (params.row.invoicedAmount >= params.row.cumulativeExpenditure) { + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense + if (params.row.invoicedAmount >= cumulativeExpenditure) { return {t("Positive")}; } else { return {t("Negative")}; @@ -112,7 +115,8 @@ const FinancialStatusByProject: React.FC = ({ headerName: "CPI", minWidth: 50, renderCell: (params: any) => { - var cpi = params.row.invoicedAmount/(params.row.projectExpense + params.row.invoicedAmount) || 0 + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense + var cpi = params.row.invoicedAmount/cumulativeExpenditure || 0 return ( = 1 ? greenColor : redColor}> {cpi.toLocaleString(undefined, { @@ -129,7 +133,7 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Projected Cash Flow Status"), minWidth: 100, renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense if (params.row.totalFee >= cumulativeExpenditure) { return {t("Positive")}; } else { @@ -143,7 +147,8 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Projected CPI"), minWidth: 50, renderCell: (params: any) => { - var projectedCpi = params.row.totalFee/(params.row.projectExpense + params.row.invoicedAmount) == Infinity ? 'N/A' : params.row.totalFee/(params.row.projectExpense + params.row.invoicedAmount) + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense + var projectedCpi = params.row.totalFee/cumulativeExpenditure == Infinity ? 'N/A' : params.row.totalFee/cumulativeExpenditure return ( = 1 ? greenColor : redColor)} @@ -199,7 +204,7 @@ const FinancialStatusByProject: React.FC = ({ minWidth: 250, type: "number", renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense return ( $ @@ -341,7 +346,7 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Cash Flow Status"), minWidth: 100, renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense return params.row.invoicedAmount >= cumulativeExpenditure ? {t("Positive")} : {t("Negative")} @@ -353,7 +358,7 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("CPI"), minWidth: 50, renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense var cpi = cumulativeExpenditure != 0 ? params.row.invoicedAmount/cumulativeExpenditure : 0 var cpiString = cpi.toLocaleString(undefined, { minimumFractionDigits: 2, @@ -370,8 +375,8 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Projected Cash Flow Status"), minWidth: 100, renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount - var status = params.row.invoiceAmount >= cumulativeExpenditure + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense + var status = params.row.totalFee >= cumulativeExpenditure return status ? {t("Positive")} : {t("Negative")} @@ -383,7 +388,7 @@ const FinancialStatusByProject: React.FC = ({ headerName: t("Projected CPI"), minWidth: 50, renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense var projectCpi = cumulativeExpenditure != 0 ? params.row.totalFee/cumulativeExpenditure : 0 var projectCpiString = projectCpi.toLocaleString(undefined, { minimumFractionDigits: 2, @@ -439,7 +444,7 @@ const FinancialStatusByProject: React.FC = ({ minWidth: 280, type: "number", renderCell: (params: any) => { - var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount + var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense return ( $ @@ -544,64 +549,104 @@ const FinancialStatusByProject: React.FC = ({ }, ]; + const handleExportByClient = async () => { + const response = await exportFinancialSummaryV2ByClientExcel(filteredByClientRows) + if (response) { + downloadFile(new Uint8Array(response.blobValue), response.filename!!) + } + console.log(filteredByClientRows) + }; + + const handleExportByProject = async () => { + const response = await exportFinancialSummaryV2ByProjectExcel(filteredByProjectRows) + if (response) { + downloadFile(new Uint8Array(response.blobValue), response.filename!!) + } + console.log(filteredByProjectRows) + }; + return ( <> - { - console.log(query) - if (query.projectCode.length > 0 || query.projectName.length > 0) { - setFilteredByProjectRows( - financialSummByProject.filter( - (cp) => - cp.projectCode.toLowerCase().includes(query.projectCode.trim().toLowerCase()) && - cp.projectName.toLowerCase().includes(query.projectName.trim().toLowerCase()) - ), - ); - } else { - setFilteredByProjectRows(financialSummByProject) - } - }} - /> -
- +
+ +
+
+ {filteredByProjectRows.length > 0 && ( + + )} +
+ { + console.log(query) + if (query.projectCode.length > 0 || query.projectName.length > 0) { + setFilteredByProjectRows( + financialSummByProject.filter( + (cp) => + cp.projectCode.toLowerCase().includes(query.projectCode.trim().toLowerCase()) && + cp.projectName.toLowerCase().includes(query.projectName.trim().toLowerCase()) + ), + ); + } else { + setFilteredByProjectRows(financialSummByProject) + } + }} /> -
+
+ +
{/* items={filteredStaff} columns={columns} /> */} +
- { - console.log(query) - if (query.customerCode.length > 0 || query.customerName.length > 0) { - setFilteredByClientRows( - financialSummByClient.filter( - (cp) => - cp.customerCode.toLowerCase().includes(query.customerCode.trim().toLowerCase()) && - cp.customerName.toLowerCase().includes(query.customerName.trim().toLowerCase()) - ), - ); - } else { - setFilteredByClientRows(financialSummByClient) - } - }} - /> -
- +
+ +
+
+ {filteredByProjectRows.length > 0 && ( + + )} +
+ { + console.log(query) + if (query.customerCode.length > 0 || query.customerName.length > 0) { + setFilteredByClientRows( + financialSummByClient.filter( + (cp) => + cp.customerCode.toLowerCase().includes(query.customerCode.trim().toLowerCase()) && + cp.customerName.toLowerCase().includes(query.customerName.trim().toLowerCase()) + ), + ); + } else { + setFilteredByClientRows(financialSummByClient) + } + }} /> -
+
+ +
+
); From b7cfabed334112a2b9fd62dd2a3c90ac2ffc572b Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 21 Jan 2025 18:31:28 +0800 Subject: [PATCH 02/11] update --- src/components/CreateProject/CreateProjectWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx index 38f047f..57d009d 100644 --- a/src/components/CreateProject/CreateProjectWrapper.tsx +++ b/src/components/CreateProject/CreateProjectWrapper.tsx @@ -74,7 +74,7 @@ const CreateProjectWrapper: React.FC = async (props) => { var filteredTeamLeads = teamId ? teamLeads.filter( (teamLead) => teamLead.teamId === teamId, ) : teamLeads - if (userStaff.id = 1) { + if (userStaff.id == 1) { filteredTeamLeads = teamLeads.filter( (teamLead) => teamLead.teamId === teamId || teamLead.team == "ST", ) From dd00a5e37330ae41776cadc356c19e1224e58843 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 21 Jan 2025 18:43:46 +0800 Subject: [PATCH 03/11] update --- src/components/CreateProject/CreateProjectWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx index 57d009d..2f22c04 100644 --- a/src/components/CreateProject/CreateProjectWrapper.tsx +++ b/src/components/CreateProject/CreateProjectWrapper.tsx @@ -74,7 +74,7 @@ const CreateProjectWrapper: React.FC = async (props) => { var filteredTeamLeads = teamId ? teamLeads.filter( (teamLead) => teamLead.teamId === teamId, ) : teamLeads - if (userStaff.id == 1) { + if (userStaff.id && userStaff.id == 1) { filteredTeamLeads = teamLeads.filter( (teamLead) => teamLead.teamId === teamId || teamLead.team == "ST", ) From 1d37b45088b07afa3441aa0480fbbda55d17ffcc Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 23 Jan 2025 11:56:10 +0800 Subject: [PATCH 04/11] update --- src/components/CreateProject/CreateProjectWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx index 2f22c04..34c4f07 100644 --- a/src/components/CreateProject/CreateProjectWrapper.tsx +++ b/src/components/CreateProject/CreateProjectWrapper.tsx @@ -74,7 +74,7 @@ const CreateProjectWrapper: React.FC = async (props) => { var filteredTeamLeads = teamId ? teamLeads.filter( (teamLead) => teamLead.teamId === teamId, ) : teamLeads - if (userStaff.id && userStaff.id == 1) { + if (userStaff?.id !== null && userStaff?.id == 1) { filteredTeamLeads = teamLeads.filter( (teamLead) => teamLead.teamId === teamId || teamLead.team == "ST", ) From 7ba7f838c9ddc1b83dc456460533b6e203aca619 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 23 Jan 2025 17:40:30 +0800 Subject: [PATCH 05/11] Add copy project function --- src/app/(main)/projects/copy/not-found.tsx | 17 ++++ src/app/(main)/projects/copy/page.tsx | 77 ++++++++++++++++++ src/app/(main)/projects/copySub/not-found.tsx | 17 ++++ src/app/(main)/projects/copySub/page.tsx | 79 +++++++++++++++++++ .../CreateProject/CreateProject.tsx | 4 +- .../CreateProject/CreateProjectWrapper.tsx | 34 ++++++-- .../ProjectSearch/ProjectSearch.tsx | 22 ++++++ .../SearchResults/SearchResults.tsx | 19 ++++- 8 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 src/app/(main)/projects/copy/not-found.tsx create mode 100644 src/app/(main)/projects/copy/page.tsx create mode 100644 src/app/(main)/projects/copySub/not-found.tsx create mode 100644 src/app/(main)/projects/copySub/page.tsx diff --git a/src/app/(main)/projects/copy/not-found.tsx b/src/app/(main)/projects/copy/not-found.tsx new file mode 100644 index 0000000..14e0e6d --- /dev/null +++ b/src/app/(main)/projects/copy/not-found.tsx @@ -0,0 +1,17 @@ +import { getServerI18n } from "@/i18n"; +import { Stack, Typography, Link } from "@mui/material"; +import NextLink from "next/link"; + +export default async function NotFound() { + const { t } = await getServerI18n("projects", "common"); + + return ( + + {t("Not Found")} + {t("The project was not found!")} + + {t("Return to all projects")} + + + ); +} diff --git a/src/app/(main)/projects/copy/page.tsx b/src/app/(main)/projects/copy/page.tsx new file mode 100644 index 0000000..8ff2777 --- /dev/null +++ b/src/app/(main)/projects/copy/page.tsx @@ -0,0 +1,77 @@ +import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer"; +import { fetchGrades } from "@/app/api/grades"; +import { + fetchProjectBuildingTypes, + fetchProjectCategories, + fetchProjectContractTypes, + fetchProjectDetails, + fetchProjectFundingTypes, + fetchProjectLocationTypes, + fetchProjectServiceTypes, + fetchProjectWorkNatures, +} from "@/app/api/projects"; +import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; +import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; +import { fetchUserAbilities } from "@/app/utils/fetchUtil"; +import { ServerFetchError } from "@/app/utils/fetchUtil"; +import CreateProject from "@/components/CreateProject"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { MAINTAIN_PROJECT } from "@/middleware"; +import Typography from "@mui/material/Typography"; +import { isArray } from "lodash"; +import { Metadata } from "next"; +import { notFound } from "next/navigation"; + +interface Props { + searchParams: { [key: string]: string | string[] | undefined }; +} + +export const metadata: Metadata = { + title: "Copy Project", +}; + +const Projects: React.FC = async ({ searchParams }) => { + const { t } = await getServerI18n("projects"); + // Assume projectId is string here + const projectId = searchParams["id"]; + const abilities = await fetchUserAbilities() + + if (!projectId || isArray(projectId) || ![MAINTAIN_PROJECT].some(ability => abilities.includes(ability))) { + notFound(); + } + + // Preload necessary dependencies + fetchAllTasks(); + fetchTaskTemplates(); + fetchProjectCategories(); + fetchProjectContractTypes(); + fetchProjectFundingTypes(); + fetchProjectLocationTypes(); + fetchProjectServiceTypes(); + fetchProjectBuildingTypes(); + fetchProjectWorkNatures(); + fetchAllCustomers(); + fetchAllSubsidiaries(); + fetchGrades(); + preloadTeamLeads(); + preloadStaff(); + + try { + console.log(projectId) + await fetchProjectDetails(projectId); + } catch (e) { + if (e instanceof ServerFetchError && e.response?.status === 404) { + notFound(); + } + } + + return ( + <> + + + + + ); +}; + +export default Projects; diff --git a/src/app/(main)/projects/copySub/not-found.tsx b/src/app/(main)/projects/copySub/not-found.tsx new file mode 100644 index 0000000..234e436 --- /dev/null +++ b/src/app/(main)/projects/copySub/not-found.tsx @@ -0,0 +1,17 @@ +import { getServerI18n } from "@/i18n"; +import { Stack, Typography, Link } from "@mui/material"; +import NextLink from "next/link"; + +export default async function NotFound() { + const { t } = await getServerI18n("projects", "common"); + + return ( + + {t("Not Found")} + {t("The sub project was not found!")} + + {t("Return to all projects")} + + + ); +} diff --git a/src/app/(main)/projects/copySub/page.tsx b/src/app/(main)/projects/copySub/page.tsx new file mode 100644 index 0000000..13b37da --- /dev/null +++ b/src/app/(main)/projects/copySub/page.tsx @@ -0,0 +1,79 @@ +import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer"; +import { fetchGrades } from "@/app/api/grades"; +import { + fetchMainProjects, + fetchProjectBuildingTypes, + fetchProjectCategories, + fetchProjectContractTypes, + fetchProjectDetails, + fetchProjectFundingTypes, + fetchProjectLocationTypes, + fetchProjectServiceTypes, + fetchProjectWorkNatures, +} from "@/app/api/projects"; +import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; +import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; +import { fetchUserAbilities } from "@/app/utils/fetchUtil"; +import CreateProject from "@/components/CreateProject"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { MAINTAIN_PROJECT } from "@/middleware"; +import Typography from "@mui/material/Typography"; +import { isArray } from "lodash"; +import { Metadata } from "next"; +import { notFound } from "next/navigation"; + +interface Props { + searchParams: { [key: string]: string | string[] | undefined }; +} + +export const metadata: Metadata = { + title: "Edit Sub Project", +}; + +const Projects: React.FC = async ({ searchParams }) => { + const { t } = await getServerI18n("projects"); + const projectId = searchParams["id"]; + + const abilities = await fetchUserAbilities() + if (!projectId || isArray(projectId) || !abilities.includes(MAINTAIN_PROJECT)) { + notFound(); + } + + // Preload necessary dependencies + fetchAllTasks(); + fetchTaskTemplates(); + fetchProjectCategories(); + fetchProjectContractTypes(); + fetchProjectFundingTypes(); + fetchProjectLocationTypes(); + fetchProjectServiceTypes(); + fetchProjectBuildingTypes(); + fetchProjectWorkNatures(); + fetchAllCustomers(); + fetchAllSubsidiaries(); + fetchGrades(); + preloadTeamLeads(); + preloadStaff(); + + try { + await fetchProjectDetails(projectId); + const data = await fetchMainProjects(); + + if (!Boolean(data) || data.length === 0) { + notFound(); + } + } catch (e) { + notFound(); + } + + return ( + <> + {t("Edit Sub Project")} + + + + + ); +}; + +export default Projects; diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index 444e22e..f4452a9 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -64,6 +64,7 @@ import { deleteDraft, loadDraft, saveToLocalStorage } from "@/app/utils/draftUti export interface Props { isEditMode: boolean; + isCopyMode: boolean; draftId?: number; isSubProject: boolean; mainProjects?: MainProject[]; @@ -116,6 +117,7 @@ const hasErrorsInTab = ( const CreateProject: React.FC = ({ isEditMode, + isCopyMode, draftId, isSubProject, mainProjects, @@ -546,7 +548,7 @@ const CreateProject: React.FC = ({ } }, [totalManhour]); - const loading = isEditMode ? !Boolean(projectName) : false; + const loading = isEditMode || isCopyMode ? !Boolean(projectName) : false; const submitDisabled = loading || diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx index 34c4f07..382a85d 100644 --- a/src/components/CreateProject/CreateProjectWrapper.tsx +++ b/src/components/CreateProject/CreateProjectWrapper.tsx @@ -21,17 +21,26 @@ import { fetchGrades } from "@/app/api/grades"; import { fetchUserAbilities, fetchUserStaff } from "@/app/utils/fetchUtil"; type CreateProjectProps = { - isEditMode: false; + isEditMode?: false; + isCopyMode?: false; isSubProject?: boolean; draftId?: number; }; interface EditProjectProps { isEditMode: true; + isCopyMode?: false; projectId?: string; isSubProject?: boolean; } -type Props = CreateProjectProps | EditProjectProps; +interface CopyProjectProps { + isEditMode?: false; + isCopyMode: true; + projectId?: string; + isSubProject?: boolean; +} + +type Props = CreateProjectProps | EditProjectProps | CopyProjectProps; const CreateProjectWrapper: React.FC = async (props) => { const [ @@ -79,7 +88,7 @@ const CreateProjectWrapper: React.FC = async (props) => { (teamLead) => teamLead.teamId === teamId || teamLead.team == "ST", ) } - const projectInfo = props.isEditMode + const projectInfo = props.isEditMode || props.isCopyMode ? await fetchProjectDetails(props.projectId!) : undefined; @@ -87,10 +96,25 @@ const CreateProjectWrapper: React.FC = async (props) => { ? await fetchMainProjects() : undefined; + if (props.isCopyMode && projectInfo) { + projectInfo.projectId = null + projectInfo.projectCode = projectInfo.projectCode + "-copy" + projectInfo.projectName = projectInfo.projectName + "-copy" + projectInfo.projectStatus = "" + Object.entries(projectInfo.milestones).forEach(([key, value]) => { + projectInfo.milestones[Number(key)].payments.forEach(({ ...rest}, idx, orig) => { + orig[idx] = { ...rest, id: rest.id * -1 } + }) + + // console.log(projectInfo.milestones[Number(key)].payments) + }) + } + return ( = ({ [router], ); + const onProjectCopyClick = useCallback( + (project: ProjectResultOrDraft) => { + if (!project.isDraft) { + if (Boolean(project.mainProject)) { + router.push(`/projects/copySub?id=${project.id}`); + } else router.push(`/projects/copy?id=${project.id}`); + } + }, + [router], + ); + const columns = useMemo[]>( () => [ { @@ -138,6 +150,16 @@ const ProjectSearch: React.FC = ({ buttonIcon: , disabled: !abilities.includes(MAINTAIN_PROJECT), }, + { + name: "id", + label: t("Copy"), + onClick: onProjectCopyClick, + buttonIcon: , + disabled: !abilities.includes(MAINTAIN_PROJECT), + disabledRows: { + status: ["Draft"] + } + }, { name: "code", label: t("Project Code") }, { name: "name", label: t("Project Name") }, { name: "category", label: t("Project Category") }, diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index 05bdf1c..2fe6bf3 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -35,6 +35,7 @@ interface ColumnWithAction extends BaseColumn { onClick: (item: T) => void; buttonIcon: React.ReactNode; disabled?: boolean; + disabledRows?: { [columnName in keyof T]: string[] }; // Filter the row which is going to be disabled } export type Column = @@ -84,6 +85,22 @@ function SearchResults({ setPage(0); }; + const disabledRows = ( + column: ColumnWithAction, + item: T + ): Boolean => { + if (column.disabledRows) { + for (const [key, value] of Object.entries(column.disabledRows)) { + if (value + .map(v => v.toLowerCase()) + .includes(String(item[key as keyof T]).toLowerCase()) + ) return true; + } + } + + return false; + }; + const table = ( <> @@ -112,7 +129,7 @@ function SearchResults({ column.onClick(item)} - disabled={Boolean(column.disabled)} + disabled={Boolean(column.disabled) || Boolean(disabledRows(column, item))} > {column.buttonIcon} From d7d41e6e62447d7131bd9c79338d08751f5adec8 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 23 Jan 2025 17:42:23 +0800 Subject: [PATCH 06/11] update financial summ --- src/app/api/financialsummary/index.ts | 1 + .../FinancialSummary.tsx | 173 ++++++++++-------- .../ProjectFinancialSummaryV2/gptFn.tsx | 6 + 3 files changed, 106 insertions(+), 74 deletions(-) diff --git a/src/app/api/financialsummary/index.ts b/src/app/api/financialsummary/index.ts index e964faf..9bf55f2 100644 --- a/src/app/api/financialsummary/index.ts +++ b/src/app/api/financialsummary/index.ts @@ -33,6 +33,7 @@ export type FinancialByProject = { totalBudget: number, manhourExpense: number, invoicedAmount: number, + uninvoicedAmount: number, paidAmount: number, projectExpense: number, } diff --git a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx index 0c8a1de..ff6e680 100644 --- a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx @@ -14,36 +14,53 @@ interface Props { financialSummByProject: FinancialByProject[] } - type InputDate = { - startDate: string; - endDate: string; - } +type InputDate = { + startDate: string; + endDate: string; +} + +type DateParams = { + [key: number]: InputDate; +} + +type ComboParams = { + [key: number]: string; +} + - type DateParams = { - 0: InputDate; - 2: InputDate; - 3: InputDate; - 4: InputDate; - 5: InputDate; - } const FinancialSummaryPage: React.FC = ({ _teamId, financialSummByProject }) => { console.log(financialSummByProject) const { t } = useTranslation(); - const curr = useMemo(() => dayjs().format(INPUT_DATE_FORMAT), []) - const currYear = useMemo(() => dayjs().get("year"), []) + var currDate: string = useMemo(() => dayjs().format(INPUT_DATE_FORMAT), []) + var currYear: number = useMemo(() => dayjs().get("year"), []) + // testing date + // currDate = "2024-10-28" + // currYear = 2024 const startDate = "10-01" const endDate = "09-30" - const currFinancialYear = useMemo(() => curr > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) + const lengthOfCombo = 10 + const currFinancialYear = useMemo(() => currDate > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) const [mainData, setMainData] = useState(financialSummByProject) const [byTeam, setByTeam] = useState(() => sumUpByTeam(mainData)) // do fetch to set const [byProject, setByProject] = useState(financialSummByProject) const [byClient, setByClient] = useState(() => sumUpByClient(mainData)) const [isLoading, setIsLoading] = useState(false) - const allTeam = useMemo(()=> { - var _allTeam: SumOfByTeam = { + const allTeam = useMemo(() => { + return byTeam.reduce((acc, curr) => ({ + id: 0, + team: "All Team", + totalFee: acc.totalFee + curr.totalFee, + totalBudget: acc.totalBudget + curr.totalBudget, + manhourExpense: acc.manhourExpense + curr.manhourExpense, + projectExpense: acc.projectExpense + curr.projectExpense, + invoicedAmount: acc.invoicedAmount + curr.invoicedAmount, + uninvoicedAmount: acc.uninvoicedAmount + curr.uninvoicedAmount, + paidAmount: acc.paidAmount + curr.paidAmount, + activeProject: acc.activeProject + curr.activeProject + }), { id: 0, team: "All Team", totalFee: 0, @@ -51,34 +68,60 @@ const FinancialSummaryPage: React.FC = ({ manhourExpense: 0, projectExpense: 0, invoicedAmount: 0, + uninvoicedAmount: 0, paidAmount: 0, - activeProject: 0, - } - for (let i = 0; i < byTeam.length; i++) { - var curr = byTeam[i] - _allTeam["totalFee"] += curr.totalFee - _allTeam["totalBudget"] += curr.totalBudget - _allTeam["manhourExpense"] += curr.manhourExpense - _allTeam["projectExpense"] += curr.projectExpense - _allTeam["invoicedAmount"] += curr.invoicedAmount - _allTeam["paidAmount"] += curr.paidAmount - _allTeam["activeProject"] += curr.activeProject - } - return _allTeam + activeProject: 0 + }) }, [mainData]) - console.log(allTeam) const [teamId, setTeamId] = useState(_teamId) const [isCardClickedIndex, setIsCardClickedIndex] = useState(_teamId || 0); const [period, setPeriod] = useState(0); - const dateMap: DateParams = useMemo(() => ({ - 0: {startDate: "", endDate: ""}, - 2: {startDate: `${currFinancialYear-2}-${startDate}`, endDate: `${currFinancialYear-1}-${endDate}`}, - 3: {startDate: `${currFinancialYear-3}-${startDate}`, endDate: `${currFinancialYear-2}-${endDate}`}, - 4: {startDate: `${currFinancialYear-4}-${startDate}`, endDate: `${currFinancialYear-3}-${endDate}`}, - 5: {startDate: "", endDate: `${currFinancialYear-4}-${endDate}`}, - }), [currYear, startDate, endDate]) + const dateMap: DateParams = useMemo(() => { + const thisYear = currDate <= `${currYear}-${endDate}` ? + {startDate: `${currYear-1}-${startDate}`, endDate: `${currYear}-${endDate}`} : + {startDate: `${currYear}-${startDate}`, endDate: `${currFinancialYear}-${endDate}`} + const map: DateParams = { + 0: {startDate: "", endDate: ""}, + 1: thisYear, + [lengthOfCombo - 1]: {startDate: "", endDate: `${currFinancialYear-(lengthOfCombo - 2)}-${endDate}`} + } + for (let i = 2; i < lengthOfCombo - 1; i++) { + map[i] = { + startDate: `${currFinancialYear-i}-${startDate}`, + endDate: `${currFinancialYear-(i - 1)}-${endDate}` + } + } + return map + }, [currDate, currYear, currFinancialYear, startDate, endDate, lengthOfCombo]) + + // const comboList: string[] = useMemo(() => { + // const list = ["All"] + // var lastYear = "" + // for (let i = 1; i < lengthOfCombo; i++) { + // var currYearStr = t(" (current year) ") + // var yearsStr = `${currFinancialYear - i} - ${currFinancialYear - i + 1}` + // var str = yearsStr + // if (i == 1) str = yearsStr + currYearStr + // lastYear = `${currFinancialYear - i}` + // list.push(str) + // } + // list[lengthOfCombo - 1] = `< ${lastYear}` + // return list + // }, []) + + const comboList: string[] = useMemo(() => { + const list = ["All"]; + for (let i = 1; i < lengthOfCombo - 1; i++) { + const yearRange = `${currFinancialYear - i} - ${currFinancialYear - i + 1}`; + const label = i === 1 ? `${yearRange} ${t("(current year)")}` : yearRange; + list.push(label); + } + const oldestYear = currFinancialYear - (lengthOfCombo - 2); + list.push(`< ${oldestYear}`); + return list; + }, [currFinancialYear, lengthOfCombo, t]); const fetchFinancialSummaryByProject = useCallback(async (endDate: string, startDate: string) => { setIsLoading(true) @@ -95,27 +138,14 @@ const FinancialSummaryPage: React.FC = ({ setTeamId(teamId) }, []); - const handleFilter = useCallback((value: number) => { + const handleFilter = useCallback(async (value: number) => { setPeriod(value) console.log(value) - var _startDate: string = "" - var _endDate = "" - if (value == 1) { - if (curr <= `${currYear}-${endDate}`) { - _startDate = `${currYear - 1}-${startDate}` - _endDate = `${currYear}-${endDate}` - } else { - _startDate = `${currYear}-${startDate}` - _endDate = `${currFinancialYear}-${endDate}` - } - } else { - _startDate = dateMap[value as keyof DateParams].startDate - _endDate = dateMap[value as keyof DateParams].endDate - } + var _startDate = dateMap[value as keyof DateParams].startDate + var _endDate = dateMap[value as keyof DateParams].endDate console.log(_startDate) console.log(_endDate) - fetchFinancialSummaryByProject(_endDate, _startDate) - + await fetchFinancialSummaryByProject(_endDate, _startDate) }, [isCardClickedIndex]) useEffect(() => { @@ -144,18 +174,11 @@ const FinancialSummaryPage: React.FC = ({ label="Age" onChange={(e) => handleFilter(Number(e.target.value))} > - {Array.from({ length: 6 }).map((_, i) => { - if (i == 0) { - return {`All`} - } else if (i == 1) { - return {`${currFinancialYear - i} - ${currFinancialYear - i + 1} (current year)`} - } else if (i == 5) { - return {`< ${currYear - i + 1}`} - } else { - return {`${currFinancialYear - i} - ${currFinancialYear - i + 1}`} - } - } - )} + { + comboList.map((str, i) => { + return {str} + }) + } @@ -168,20 +191,21 @@ const FinancialSummaryPage: React.FC = ({ {_teamId == 0 && allTeam &&
handleCardClick(0)}> = (allTeam.projectExpense + allTeam.manhourExpense) ? "Positive" : "Negative"} - CostPerformanceIndex={allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense) || 0} + CostPerformanceIndex={isFinite(allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense) || 0} ProjectedCashFlowStatus={allTeam.totalFee >= (allTeam.projectExpense + allTeam.manhourExpense) ? "Positive" : "Negative"} - ProjectedCPI={allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense)} + ProjectedCPI={isFinite(allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense) || 0} ClickedIndex={isCardClickedIndex} Index={0}/>
} @@ -196,12 +220,13 @@ const FinancialSummaryPage: React.FC = ({ TotalCumulative={record.projectExpense + record.manhourExpense} TotalProjectExpense={record.projectExpense} TotalInvoicedAmount={record.invoicedAmount} - TotalUnInvoicedAmount={Math.abs(record.totalFee - record.invoicedAmount)} + // TotalUnInvoicedAmount={Math.abs(record.totalFee - record.invoicedAmount)} + TotalUnInvoicedAmount={Math.abs(record.uninvoicedAmount)} TotalReceivedAmount={record.paidAmount} CashFlowStatus={record.invoicedAmount >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"} CostPerformanceIndex={record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0} ProjectedCashFlowStatus={record.totalFee >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"} - ProjectedCPI={record.totalFee/(record.projectExpense + record.manhourExpense)} + ProjectedCPI={record.totalFee/(record.projectExpense + record.manhourExpense) || 0} ClickedIndex={isCardClickedIndex} Index={record.id}/> diff --git a/src/components/ProjectFinancialSummaryV2/gptFn.tsx b/src/components/ProjectFinancialSummaryV2/gptFn.tsx index 4404029..8965484 100644 --- a/src/components/ProjectFinancialSummaryV2/gptFn.tsx +++ b/src/components/ProjectFinancialSummaryV2/gptFn.tsx @@ -8,6 +8,7 @@ export type SumOfByTeam = { manhourExpense: number, projectExpense: number, invoicedAmount: number, + uninvoicedAmount: number, paidAmount: number, activeProject: number, } @@ -21,6 +22,7 @@ export type SumOfByClient = { manhourExpense: number, projectExpense: number, invoicedAmount: number, + uninvoicedAmount: number, paidAmount: number, sumOfProjects: number, } @@ -36,6 +38,7 @@ export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] { manhourExpense: 0, projectExpense: 0, invoicedAmount: 0, + uninvoicedAmount: 0, paidAmount: 0, sumOfProjects: 0 }; @@ -46,6 +49,7 @@ export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] { acc[item.custId].manhourExpense += item.manhourExpense; acc[item.custId].projectExpense += item.projectExpense; acc[item.custId].invoicedAmount += item.invoicedAmount; + acc[item.custId].uninvoicedAmount += item.uninvoicedAmount; acc[item.custId].paidAmount += item.paidAmount; acc[item.custId].sumOfProjects += 1; @@ -64,6 +68,7 @@ export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] { manhourExpense: 0, projectExpense: 0, invoicedAmount: 0, + uninvoicedAmount: 0, paidAmount: 0, activeProject: 0 }; @@ -75,6 +80,7 @@ export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] { acc[item.teamId].manhourExpense += item.manhourExpense; acc[item.teamId].projectExpense += item.projectExpense; acc[item.teamId].invoicedAmount += item.invoicedAmount; + acc[item.teamId].uninvoicedAmount += item.uninvoicedAmount; acc[item.teamId].paidAmount += item.paidAmount; acc[item.teamId].activeProject += 1; From 179095d589eaef4f7269b21cb6ec3f9a1b581a0a Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 27 Jan 2025 12:17:10 +0800 Subject: [PATCH 07/11] update finan summ --- .../FinnancialStatusByProject.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx index 63f53bb..cf58e73 100644 --- a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx @@ -277,7 +277,9 @@ const FinancialStatusByProject: React.FC = ({ minWidth: 250, type: "number", renderCell: (params: any) => { - var nonInvoiced = params.row.totalFee - params.row.invoicedAmount + var fee = params.row.totalFee + var invoiced = params.row.invoicedAmount + var nonInvoiced = fee - invoiced < 0 ? 0 : fee - invoiced return ( $ @@ -517,11 +519,13 @@ const FinancialStatusByProject: React.FC = ({ minWidth: 250, type: "number", renderCell: (params: any) => { - var uninvoiced = params.row.totalFee - params.row.invoicedAmount + var fee = params.row.totalFee + var invoiced = params.row.invoicedAmount + var nonInvoiced = fee - invoiced < 0 ? 0 : fee - invoiced return ( $ - {uninvoiced.toLocaleString(undefined, { + {nonInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2, })} From 4c31dd54b08b5d909a1cd958a89a824447f48ad9 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 27 Jan 2025 16:24:52 +0800 Subject: [PATCH 08/11] finan summ update : uninvoiced --- .../ProjectFinancialSummaryV2/FinancialSummary.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx index ff6e680..ca78dad 100644 --- a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx @@ -199,8 +199,8 @@ const FinancialSummaryPage: React.FC = ({ TotalCumulative={allTeam.manhourExpense + allTeam.projectExpense} TotalProjectExpense={allTeam.projectExpense} TotalInvoicedAmount={allTeam.invoicedAmount} - // TotalUnInvoicedAmount={allTeam.totalFee - allTeam.invoicedAmount} - TotalUnInvoicedAmount={allTeam.uninvoicedAmount} + TotalUnInvoicedAmount={allTeam.totalFee - allTeam.invoicedAmount} + // TotalUnInvoicedAmount={allTeam.uninvoicedAmount} TotalReceivedAmount={allTeam.paidAmount} CashFlowStatus={allTeam.invoicedAmount >= (allTeam.projectExpense + allTeam.manhourExpense) ? "Positive" : "Negative"} CostPerformanceIndex={isFinite(allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense) || 0} @@ -220,8 +220,8 @@ const FinancialSummaryPage: React.FC = ({ TotalCumulative={record.projectExpense + record.manhourExpense} TotalProjectExpense={record.projectExpense} TotalInvoicedAmount={record.invoicedAmount} - // TotalUnInvoicedAmount={Math.abs(record.totalFee - record.invoicedAmount)} - TotalUnInvoicedAmount={Math.abs(record.uninvoicedAmount)} + TotalUnInvoicedAmount={Math.abs(record.totalFee - record.invoicedAmount)} + // TotalUnInvoicedAmount={Math.abs(record.uninvoicedAmount)} TotalReceivedAmount={record.paidAmount} CashFlowStatus={record.invoicedAmount >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"} CostPerformanceIndex={record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0} From c8e3ba9130e2cb2233fd9ce4f6990bf527b75943 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 27 Jan 2025 17:30:42 +0800 Subject: [PATCH 09/11] update finan --- .../FinancialSummary.tsx | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx index ca78dad..5462ad7 100644 --- a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx @@ -23,11 +23,6 @@ type DateParams = { [key: number]: InputDate; } -type ComboParams = { - [key: number]: string; -} - - const FinancialSummaryPage: React.FC = ({ _teamId, financialSummByProject @@ -41,7 +36,7 @@ const FinancialSummaryPage: React.FC = ({ // currYear = 2024 const startDate = "10-01" const endDate = "09-30" - const lengthOfCombo = 10 + const lengthOfYear = 5 const currFinancialYear = useMemo(() => currDate > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) const [mainData, setMainData] = useState(financialSummByProject) const [byTeam, setByTeam] = useState(() => sumUpByTeam(mainData)) // do fetch to set @@ -85,16 +80,16 @@ const FinancialSummaryPage: React.FC = ({ const map: DateParams = { 0: {startDate: "", endDate: ""}, 1: thisYear, - [lengthOfCombo - 1]: {startDate: "", endDate: `${currFinancialYear-(lengthOfCombo - 2)}-${endDate}`} + [lengthOfYear - 1]: {startDate: "", endDate: `${currFinancialYear-(lengthOfYear - 2)}-${endDate}`} } - for (let i = 2; i < lengthOfCombo - 1; i++) { + for (let i = 2; i < lengthOfYear - 1; i++) { map[i] = { startDate: `${currFinancialYear-i}-${startDate}`, endDate: `${currFinancialYear-(i - 1)}-${endDate}` } } return map - }, [currDate, currYear, currFinancialYear, startDate, endDate, lengthOfCombo]) + }, [currDate, currYear, currFinancialYear, startDate, endDate, lengthOfYear]) // const comboList: string[] = useMemo(() => { // const list = ["All"] @@ -113,15 +108,15 @@ const FinancialSummaryPage: React.FC = ({ const comboList: string[] = useMemo(() => { const list = ["All"]; - for (let i = 1; i < lengthOfCombo - 1; i++) { + for (let i = 1; i < lengthOfYear - 1; i++) { const yearRange = `${currFinancialYear - i} - ${currFinancialYear - i + 1}`; const label = i === 1 ? `${yearRange} ${t("(current year)")}` : yearRange; list.push(label); } - const oldestYear = currFinancialYear - (lengthOfCombo - 2); + const oldestYear = currFinancialYear - (lengthOfYear - 2); list.push(`< ${oldestYear}`); return list; - }, [currFinancialYear, lengthOfCombo, t]); + }, [currFinancialYear, lengthOfYear, t]); const fetchFinancialSummaryByProject = useCallback(async (endDate: string, startDate: string) => { setIsLoading(true) @@ -158,7 +153,7 @@ const FinancialSummaryPage: React.FC = ({ setByClient(sumUpByClient(mainData)) } }, [teamId]) - + return ( <> @@ -189,7 +184,7 @@ const FinancialSummaryPage: React.FC = ({
{_teamId == 0 && allTeam && -
handleCardClick(0)}> +
handleCardClick(0)}> = ({ // TotalUnInvoicedAmount={allTeam.uninvoicedAmount} TotalReceivedAmount={allTeam.paidAmount} CashFlowStatus={allTeam.invoicedAmount >= (allTeam.projectExpense + allTeam.manhourExpense) ? "Positive" : "Negative"} - CostPerformanceIndex={isFinite(allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense) || 0} + CostPerformanceIndex={!isFinite(allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.invoicedAmount/(allTeam.projectExpense + allTeam.manhourExpense) || 0} ProjectedCashFlowStatus={allTeam.totalFee >= (allTeam.projectExpense + allTeam.manhourExpense) ? "Positive" : "Negative"} - ProjectedCPI={isFinite(allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense) || 0} + ProjectedCPI={!isFinite(allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense)) ? 0 : allTeam.totalFee/(allTeam.projectExpense + allTeam.manhourExpense) || 0} ClickedIndex={isCardClickedIndex} Index={0}/>
} {byTeam.length > 0 && byTeam.map((record) => ( -
handleCardClick(record.id)}> +
handleCardClick(record.id)}> = ({ // TotalUnInvoicedAmount={Math.abs(record.uninvoicedAmount)} TotalReceivedAmount={record.paidAmount} CashFlowStatus={record.invoicedAmount >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"} - CostPerformanceIndex={record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0} + CostPerformanceIndex={!isFinite(record.invoicedAmount/(record.projectExpense + record.manhourExpense)) ? 0 : record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0} ProjectedCashFlowStatus={record.totalFee >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"} - ProjectedCPI={record.totalFee/(record.projectExpense + record.manhourExpense) || 0} + ProjectedCPI={!isFinite(record.totalFee/(record.projectExpense + record.manhourExpense)) ? 0 : record.totalFee/(record.projectExpense + record.manhourExpense) || 0} ClickedIndex={isCardClickedIndex} Index={record.id}/>
From d41aad3e5ffcec52ea097ee2b37998a275b765ad Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 27 Jan 2025 17:31:37 +0800 Subject: [PATCH 10/11] update --- .../ProjectFinancialSummaryV2/FinancialSummary.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx index 5462ad7..08dc47b 100644 --- a/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx +++ b/src/components/ProjectFinancialSummaryV2/FinancialSummary.tsx @@ -36,7 +36,7 @@ const FinancialSummaryPage: React.FC = ({ // currYear = 2024 const startDate = "10-01" const endDate = "09-30" - const lengthOfYear = 5 + const lengthOfCombo = 6 const currFinancialYear = useMemo(() => currDate > `${currYear}-${startDate}` ? currYear + 1 : currYear, [currYear]) const [mainData, setMainData] = useState(financialSummByProject) const [byTeam, setByTeam] = useState(() => sumUpByTeam(mainData)) // do fetch to set @@ -80,16 +80,16 @@ const FinancialSummaryPage: React.FC = ({ const map: DateParams = { 0: {startDate: "", endDate: ""}, 1: thisYear, - [lengthOfYear - 1]: {startDate: "", endDate: `${currFinancialYear-(lengthOfYear - 2)}-${endDate}`} + [lengthOfCombo - 1]: {startDate: "", endDate: `${currFinancialYear-(lengthOfCombo - 2)}-${endDate}`} } - for (let i = 2; i < lengthOfYear - 1; i++) { + for (let i = 2; i < lengthOfCombo - 1; i++) { map[i] = { startDate: `${currFinancialYear-i}-${startDate}`, endDate: `${currFinancialYear-(i - 1)}-${endDate}` } } return map - }, [currDate, currYear, currFinancialYear, startDate, endDate, lengthOfYear]) + }, [currDate, currYear, currFinancialYear, startDate, endDate, lengthOfCombo]) // const comboList: string[] = useMemo(() => { // const list = ["All"] @@ -108,15 +108,15 @@ const FinancialSummaryPage: React.FC = ({ const comboList: string[] = useMemo(() => { const list = ["All"]; - for (let i = 1; i < lengthOfYear - 1; i++) { + for (let i = 1; i < lengthOfCombo - 1; i++) { const yearRange = `${currFinancialYear - i} - ${currFinancialYear - i + 1}`; const label = i === 1 ? `${yearRange} ${t("(current year)")}` : yearRange; list.push(label); } - const oldestYear = currFinancialYear - (lengthOfYear - 2); + const oldestYear = currFinancialYear - (lengthOfCombo - 2); list.push(`< ${oldestYear}`); return list; - }, [currFinancialYear, lengthOfYear, t]); + }, [currFinancialYear, lengthOfCombo, t]); const fetchFinancialSummaryByProject = useCallback(async (endDate: string, startDate: string) => { setIsLoading(true) From d494ea1f8ac775ef05a3a8068dc1420b86efcd9a Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 3 Feb 2025 14:55:49 +0800 Subject: [PATCH 11/11] update planStart planEnd --- src/components/CreateProject/ProjectClientDetails.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CreateProject/ProjectClientDetails.tsx b/src/components/CreateProject/ProjectClientDetails.tsx index 2fffcd1..90f4472 100644 --- a/src/components/CreateProject/ProjectClientDetails.tsx +++ b/src/components/CreateProject/ProjectClientDetails.tsx @@ -345,7 +345,7 @@ const ProjectClientDetails: React.FC = ({ // Boolean(errors.projectPlanStart) // || new Date(planStart) > new Date(planEnd) - || !Boolean(planStart) + || Boolean(errors.projectPlanStart) , }, }} @@ -373,7 +373,7 @@ const ProjectClientDetails: React.FC = ({ // Boolean(errors.projectPlanEnd) // || new Date(planStart) > new Date(planEnd) - || !Boolean(planEnd) + || Boolean(errors.projectPlanEnd) , }, }}