From 3fc5f822d41b36fde0998a725ff46dbb7050d861 Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Thu, 16 May 2024 15:58:10 +0800 Subject: [PATCH 1/6] update project status by client --- .../dashboard/ProjectStatusByClient/page.tsx | 6 +- src/app/api/clientprojects/actions.ts | 15 +++- .../CustomDatagrid/CustomDatagrid.tsx | 2 +- .../ProgressByClient/ProgressByClient.tsx | 77 ++++++++++++++----- .../ProgressByClientSearch.tsx | 13 ++-- 5 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx b/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx index a4df131..1b51537 100644 --- a/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx +++ b/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx @@ -1,14 +1,17 @@ + import { Metadata } from "next"; import { I18nProvider } from "@/i18n"; import DashboardPage from "@/components/DashboardPage/DashboardPage"; import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; import ProgressByClientSearch from "@/components/ProgressByClientSearch"; -import { Suspense } from "react"; +import { Suspense} from "react"; import Tabs, { TabsProps } from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import Typography from "@mui/material/Typography"; import ProgressByClient from "@/components/ProgressByClient"; import { preloadClientProjects } from "@/app/api/clientprojects"; +import { ClientProjectResult} from "@/app/api/clientprojects"; +import { useSearchParams } from 'next/navigation'; export const metadata: Metadata = { title: "Project Status by Client", @@ -24,6 +27,7 @@ const ProjectStatusByClient: React.FC = () => { }> + ); }; diff --git a/src/app/api/clientprojects/actions.ts b/src/app/api/clientprojects/actions.ts index 27b80df..9de9ea5 100644 --- a/src/app/api/clientprojects/actions.ts +++ b/src/app/api/clientprojects/actions.ts @@ -21,8 +21,15 @@ export interface ClientSubsidiaryProjectResult { comingPaymentMilestone: string; } -export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, subsidiaryId: number) => { - return serverFetchJson( - `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}` - ); +export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, subsidiaryId?: number) => { + if (subsidiaryId === 0){ + return serverFetchJson( + `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}` + ); + } else { + return serverFetchJson( + `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}` + ); + } + }); diff --git a/src/components/CustomDatagrid/CustomDatagrid.tsx b/src/components/CustomDatagrid/CustomDatagrid.tsx index 4867623..c346874 100644 --- a/src/components/CustomDatagrid/CustomDatagrid.tsx +++ b/src/components/CustomDatagrid/CustomDatagrid.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; import { Card, CardHeader, CardContent, SxProps, Theme } from "@mui/material"; -import { DataGrid, GridColDef, GridRowSelectionModel, GridColumnGroupingModel} from "@mui/x-data-grid"; +import { DataGrid, GridColDef, GridRowSelectionModel, GridColumnGroupingModel, useGridApiRef} from "@mui/x-data-grid"; import { darken, lighten, styled } from "@mui/material/styles"; import { useState } from "react"; diff --git a/src/components/ProgressByClient/ProgressByClient.tsx b/src/components/ProgressByClient/ProgressByClient.tsx index 37a2e86..115c08f 100644 --- a/src/components/ProgressByClient/ProgressByClient.tsx +++ b/src/components/ProgressByClient/ProgressByClient.tsx @@ -21,13 +21,18 @@ import { ClientProjectResult} from "@/app/api/clientprojects"; import { ConstructionOutlined } from "@mui/icons-material"; import ReactApexChart from "react-apexcharts"; import { ApexOptions } from "apexcharts"; +import { useSearchParams } from 'next/navigation'; +import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions"; // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); interface Props { - clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; + // clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; } -const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => { +const ProgressByClient: React.FC = () => { + const searchParams = useSearchParams(); + const customerId = searchParams.get('customerId'); + const subsidiaryId = searchParams.get('subsidiaryId'); const [activeTab, setActiveTab] = useState("financialSummary"); const [SearchCriteria, setSearchCriteria] = React.useState({}); const { t } = useTranslation("dashboard"); @@ -56,6 +61,32 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => const [chartProjectName, setChartProjectName]:any[] = useState([]); const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b"]; + const [clientSubsidiaryProjectResult, setClientSubsidiaryProjectResult]:any[] = useState([]); + + const fetchData = async () => { + if (customerId && subsidiaryId) { + try { + if (subsidiaryId === '-'){ + console.log("ss") + const clickResult = await fetchAllClientSubsidiaryProjects( + Number(customerId),Number(0)) + console.log(clickResult) + setClientSubsidiaryProjectResult(clickResult); + } else { + const clickResult = await fetchAllClientSubsidiaryProjects( + Number(customerId), + Number(subsidiaryId)) + console.log(clickResult) + setClientSubsidiaryProjectResult(clickResult); + } + + + } catch (error) { + console.error('Error fetching client subsidiary projects:', error); + } + } + } + useEffect(() => { const projectName = [] const manhourConsumptionPercentage = [] @@ -68,6 +99,12 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => setChartManhourConsumptionPercentage(manhourConsumptionPercentage) }, [clientSubsidiaryProjectResult]); + useEffect(() => { + fetchData() + }, [customerId,subsidiaryId]); + + + const rows2 = [ { id: 1, @@ -155,37 +192,37 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => > ); }, - flex: 0.1, + flex:0.1 }, { id: "projectName", field: "projectName", headerName: "Project", - flex: 1, + minWidth:300 }, { id: "team", field: "team", headerName: "Team", - flex: 0.8, + minWidth: 50 }, { - id: "teamLeader", - field: "teamLeader", + id: "teamLead", + field: "teamLead", headerName: "Team Leader", - flex: 0.8, + minWidth: 70 }, { id: "expectedStage", field: "expectedStage", headerName: "Expected Stage", - flex: 1, + minWidth: 300 }, { id: "budgetedManhour", field: "budgetedManhour", headerName: "Budgeted Manhour", - flex: 0.8, + minWidth: 70 }, { id: "spentManhour", @@ -201,7 +238,7 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => return {params.row.spentManhour}; } }, - flex: 0.8, + minWidth: 70 }, { id: "remainedManhour", @@ -216,13 +253,13 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => return {params.row.remainedManhour}; } }, - flex: 1, + minWidth: 70 }, { id: "comingPaymentMilestone", field: "comingPaymentMilestone", headerName: "Coming Payment Milestone", - flex: 1, + minWidth: 100 }, { id: "alert", @@ -239,7 +276,7 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => return ; } }, - flex: 0.2, + flex:0.1 }, ]; const optionstest: ApexOptions = { @@ -463,7 +500,7 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => }; const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { - const selectedRowsData:any = clientSubsidiaryProjectResult.filter((row) => + const selectedRowsData:any = clientSubsidiaryProjectResult.filter((row:any) => newSelectionModel.includes(row.projectId), ); console.log(selectedRowsData); @@ -533,9 +570,9 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) =>
@@ -590,13 +627,13 @@ const ProgressByClient: React.FC = ({ clientSubsidiaryProjectResult }) => Please select the project you want to check. )} - {/* {percentageArray.length > 0 && ( + {percentageArray.length > 0 && ( - )} */} + )} diff --git a/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx index abd0333..7661856 100644 --- a/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx +++ b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx @@ -1,7 +1,7 @@ "use client"; import { ProjectResult } from "@/app/api/projects"; -import React, { useMemo, useState, useCallback } from "react"; +import React, { useMemo, useState, useCallback, useEffect } from "react"; import SearchBox, { Criterion } from "../SearchBox"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; @@ -18,6 +18,7 @@ type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const ProgressByClientSearch: React.FC = ({ clientProjects }) => { + const router = useRouter(); const { t } = useTranslation("projects"); const searchParams = useSearchParams() // If project searching is done on the server-side, then no need for this. @@ -41,9 +42,9 @@ const ProgressByClientSearch: React.FC = ({ clientProjects }) => { const onTaskClick = useCallback(async (clientProjectResult: ClientProjectResult) => { try { - const clickResult = await fetchAllClientSubsidiaryProjects(clientProjectResult.customerId, clientProjectResult.subsidiaryId); - console.log(clickResult); - setClientSubsidiaryProjectResult(clickResult); + router.push( + `/dashboard/ProjectStatusByClient?customerId=${clientProjectResult.customerId}&subsidiaryId=${clientProjectResult.subsidiaryId}` + ); } catch (error) { console.error('Error fetching client subsidiary projects:', error); } @@ -86,7 +87,9 @@ const ProgressByClientSearch: React.FC = ({ clientProjects }) => { items={filteredProjects} columns={columns} /> - + {/* {clientSubsidiaryProjectResult.length > 0 && ( + + )} */} ); }; From 8e7fb7cf20df85ed26dbafedd50beb7ba97cbeeb Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 16 May 2024 16:30:20 +0800 Subject: [PATCH 2/6] update project --- .../CreateProject/CreateProject.tsx | 36 +++++++++---------- src/components/CreateProject/Milestone.tsx | 7 ++-- .../CreateProject/MilestoneSection.tsx | 2 ++ .../CreateProject/StaffAllocation.tsx | 4 +-- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index 4242afb..fac9c79 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -170,12 +170,12 @@ const CreateProject: React.FC = ({ // Tab - Milestone let projectTotal = 0 - const milestonesKeys = Object.keys(data.milestones) + const milestonesKeys = Object.keys(data.milestones).filter(key => taskGroupKeys.includes(key)) milestonesKeys.filter(key => Object.keys(data.taskGroups).includes(key)).forEach(key => { const { startDate, endDate, payments } = data.milestones[parseFloat(key)] if (!Boolean(startDate) || startDate === "Invalid Date" || !Boolean(endDate) || endDate === "Invalid Date" || new Date(startDate) > new Date(endDate)) { - formProps.setError("milestones", {message: "milestones is not valid", type: "invalid"}) + formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" }) setTabIndex(3) hasErrors = true } @@ -183,8 +183,8 @@ const CreateProject: React.FC = ({ projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0) }) - if (projectTotal !== data.expectedProjectFee) { - formProps.setError("milestones", {message: "milestones is not valid", type: "invalid"}) + if (projectTotal !== data.expectedProjectFee || milestonesKeys.length !== taskGroupKeys.length) { + formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" }) setTabIndex(3) hasErrors = true } @@ -219,7 +219,7 @@ const CreateProject: React.FC = ({ data.projectActualEnd = dayjs().format("YYYY-MM-DD"); } - data.taskTemplateId = data.taskTemplateId === "All" ? undefined : data.taskTemplateId; + data.taskTemplateId = data.taskTemplateId === "All" ? undefined : data.taskTemplateId; const response = await saveProject(data); if (response.id > 0) { @@ -293,7 +293,7 @@ const CreateProject: React.FC = ({ {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( {/* {!formProps.getValues("projectActualStart") && ( */} - {formProps.getValues("projectStatus") === "Pending to Start" && ( + {formProps.getValues("projectStatus").toLowerCase() === "pending to start" && ( - )} + {formProps.getValues("projectStatus").toLowerCase() === "on-going" && ( + + )} {!( // formProps.getValues("projectActualStart") && // formProps.getValues("projectActualEnd") - formProps.getValues("projectStatus") === "Completed" || + formProps.getValues("projectStatus") === "Completed" || formProps.getValues("projectStatus") === "Deleted" ) && ( - - + + + + ); From b28f8125c0c423a486820f99b2d2d0e96bc5f3b4 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 16 May 2024 18:53:42 +0800 Subject: [PATCH 5/6] update report --- .../analytics/ProjectCashFlowReport/page.tsx | 7 +++- .../GenerateProjectCashFlowReport.tsx | 1 + src/components/SearchBox/SearchBox.tsx | 35 ++++++++++--------- src/i18n/en/common.json | 1 + src/i18n/zh/common.json | 1 + 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/app/(main)/analytics/ProjectCashFlowReport/page.tsx b/src/app/(main)/analytics/ProjectCashFlowReport/page.tsx index 301ef12..7ba5f7e 100644 --- a/src/app/(main)/analytics/ProjectCashFlowReport/page.tsx +++ b/src/app/(main)/analytics/ProjectCashFlowReport/page.tsx @@ -1,18 +1,23 @@ import { Metadata } from "next"; import { Suspense } from "react"; -import { I18nProvider } from "@/i18n"; +import { I18nProvider, getServerI18n } from "@/i18n"; import { fetchProjects } from "@/app/api/projects"; import GenerateProjectCashFlowReport from "@/components/GenerateProjectCashFlowReport"; +import { Typography } from "@mui/material"; export const metadata: Metadata = { title: "Project Cash Flow Report", }; const ProjectCashFlowReport: React.FC = async () => { + const { t } = await getServerI18n("reports"); fetchProjects(); return ( <> + + {t("Project Cash Flow Report")} + }> diff --git a/src/components/GenerateProjectCashFlowReport/GenerateProjectCashFlowReport.tsx b/src/components/GenerateProjectCashFlowReport/GenerateProjectCashFlowReport.tsx index 0b7d661..e23888f 100644 --- a/src/components/GenerateProjectCashFlowReport/GenerateProjectCashFlowReport.tsx +++ b/src/components/GenerateProjectCashFlowReport/GenerateProjectCashFlowReport.tsx @@ -41,6 +41,7 @@ const GenerateProjectCashFlowReport: React.FC = ({ projects }) => { } } }} + formType={"download"} /> ); diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 92eb204..716d495 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -15,6 +15,7 @@ import CardActions from "@mui/material/CardActions"; import Button from "@mui/material/Button"; import RestartAlt from "@mui/icons-material/RestartAlt"; import Search from "@mui/icons-material/Search"; +import FileDownload from '@mui/icons-material/FileDownload'; import dayjs from "dayjs"; import "dayjs/locale/zh-hk"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -58,12 +59,14 @@ interface Props { criteria: Criterion[]; onSearch: (inputs: Record) => void; onReset?: () => void; + formType?: String, } function SearchBox({ criteria, onSearch, onReset, + formType, }: Props) { const { t } = useTranslation("common"); const defaultInputs = useMemo( @@ -223,22 +226,22 @@ function SearchBox({ ); })} - - - - + + + + ); diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json index a7d019a..57932c1 100644 --- a/src/i18n/en/common.json +++ b/src/i18n/en/common.json @@ -19,6 +19,7 @@ "Details": "Details", "Delete": "Delete", + "Download": "Download", "Search": "Search", "Search Criteria": "Search Criteria", "Cancel": "Cancel", diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 4ff2fcf..ffcec1d 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -17,6 +17,7 @@ "Details": "詳情", "Delete": "刪除", + "Download": "下載", "Search": "搜尋", "Search Criteria": "搜尋條件", "Cancel": "取消", From a05f40a84db1fdac0c4efade5dce8bca5436a77e Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 16 May 2024 19:23:37 +0800 Subject: [PATCH 6/6] update --- .../GenerateMonthlyWorkHoursReport.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx b/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx index 3b5fa24..d7841b4 100644 --- a/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx +++ b/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx @@ -46,8 +46,10 @@ return ( criteria={searchCriteria} onSearch={async (query: any) => { console.log(query) - if (query.staff.length > 0 && query.staff.toLocaleLowerCase() !== "all" && query.date.length < 0) { + console.log(query.date.length) + if (query.staff.length > 0 && query.staff.toLocaleLowerCase() !== "all" && query.date.length > 0) { const index = staffCombo.findIndex(staff => staff === query.staff) + console.log(index) const response = await fetchMonthlyWorkHoursReport({ id: staffs[index].id, yearMonth: query.date }) if (response) { downloadFile(new Uint8Array(response.blobValue), response.filename!!)