浏览代码

Merge branch 'David_Branch'

tags/Baseline_30082024_FRONTEND_UAT
MSI\User 1年前
父节点
当前提交
427994cf8d
共有 17 个文件被更改,包括 383 次插入93 次删除
  1. +4
    -2
      src/app/(main)/projects/page.tsx
  2. +4
    -0
      src/app/api/clientprojects/actions.ts
  3. +2
    -2
      src/components/AppBar/Profile.tsx
  4. +1
    -1
      src/components/Breadcrumb/Breadcrumb.tsx
  5. +7
    -1
      src/components/CustomDatagrid/CustomDatagrid.tsx
  6. +11
    -5
      src/components/NavigationContent/NavigationContent.tsx
  7. +35
    -2
      src/components/ProgressByClient/ProgressByClient.tsx
  8. +29
    -2
      src/components/ProgressByTeam/ProgressByTeam.tsx
  9. +31
    -24
      src/components/ProjectCashFlow/ProjectCashFlow.tsx
  10. +167
    -41
      src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx
  11. +24
    -2
      src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx
  12. +28
    -3
      src/components/ProjectResourceConsumptionRanking/ProjectResourceConsumptionRanking.tsx
  13. +4
    -4
      src/components/ProjectSearch/ProjectSearch.tsx
  14. +2
    -1
      src/i18n/en/dashboard.json
  15. +16
    -1
      src/i18n/en/projects.json
  16. +2
    -1
      src/i18n/zh/dashboard.json
  17. +16
    -1
      src/i18n/zh/projects.json

+ 4
- 2
src/app/(main)/projects/page.tsx 查看文件

@@ -1,7 +1,7 @@
import { fetchProjectCategories, fetchProjects, preloadProjects } from "@/app/api/projects"; import { fetchProjectCategories, fetchProjects, preloadProjects } from "@/app/api/projects";
import { fetchUserAbilities } from "@/app/utils/fetchUtil"; import { fetchUserAbilities } from "@/app/utils/fetchUtil";
import ProjectSearch from "@/components/ProjectSearch"; import ProjectSearch from "@/components/ProjectSearch";
import { getServerI18n } from "@/i18n";
import { getServerI18n, I18nProvider } from "@/i18n";
import { MAINTAIN_PROJECT, VIEW_PROJECT } from "@/middleware"; import { MAINTAIN_PROJECT, VIEW_PROJECT } from "@/middleware";
import Add from "@mui/icons-material/Add"; import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@@ -28,6 +28,7 @@ const Projects: React.FC = async () => {


return ( return (
<> <>
<I18nProvider namespaces={["projects","common"]}>
<Stack <Stack
direction="row" direction="row"
justifyContent="space-between" justifyContent="space-between"
@@ -35,7 +36,7 @@ const Projects: React.FC = async () => {
rowGap={2} rowGap={2}
> >
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Projects")}
{t("Project Management")}
</Typography> </Typography>
{abilities.includes(MAINTAIN_PROJECT) && <Stack {abilities.includes(MAINTAIN_PROJECT) && <Stack
direction="row" direction="row"
@@ -66,6 +67,7 @@ const Projects: React.FC = async () => {
<Suspense fallback={<ProjectSearch.Loading />}> <Suspense fallback={<ProjectSearch.Loading />}>
<ProjectSearch /> <ProjectSearch />
</Suspense> </Suspense>
</I18nProvider>
</> </>
); );
}; };


+ 4
- 0
src/app/api/clientprojects/actions.ts 查看文件

@@ -23,6 +23,10 @@ export interface ClientSubsidiaryProjectResult {


export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => {
if (subsidiaryId === 0){ if (subsidiaryId === 0){
return serverFetchJson<ClientSubsidiaryProjectResult[]>(
`${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}&tableSorting=${tableSorting}`
);
} else if (subsidiaryId === undefined) {
return serverFetchJson<ClientSubsidiaryProjectResult[]>( return serverFetchJson<ClientSubsidiaryProjectResult[]>(
`${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}`
); );


+ 2
- 2
src/components/AppBar/Profile.tsx 查看文件

@@ -66,8 +66,8 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => {
</Typography> </Typography>
<Divider /> <Divider />
<MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem> <MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem>
{/* {language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>}
{language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>} */}
{language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>}
{language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>}
<MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem>
</Menu> </Menu>
</> </>


+ 1
- 1
src/components/Breadcrumb/Breadcrumb.tsx 查看文件

@@ -23,7 +23,7 @@ const pathToLabelMap: { [path: string]: string } = {
"/dashboard/ProjectStatusByTeam": "Project Status by Team", "/dashboard/ProjectStatusByTeam": "Project Status by Team",
"/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking",
"/dashboard/StaffUtilization": "Staff Utilization", "/dashboard/StaffUtilization": "Staff Utilization",
"/projects": "Projects",
"/projects": "Project Management",
"/projects/create": "Create Project", "/projects/create": "Create Project",
"/projects/createSub": "Sub Project", "/projects/createSub": "Sub Project",
"/projects/edit": "Edit Project", "/projects/edit": "Edit Project",


+ 7
- 1
src/components/CustomDatagrid/CustomDatagrid.tsx 查看文件

@@ -19,7 +19,8 @@ interface CustomDatagridProps {
onRowSelectionModelChange?: ( onRowSelectionModelChange?: (
newSelectionModel: GridRowSelectionModel, newSelectionModel: GridRowSelectionModel,
) => void; ) => void;
selectionModel?: any;
selectionModel?: GridRowSelectionModel;
rowSelectionModel?: GridRowSelectionModel;
columnGroupingModel?: any; columnGroupingModel?: any;
pageSize?:any; pageSize?:any;
} }
@@ -33,6 +34,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
sx, sx,
dataGridHeight, dataGridHeight,
checkboxSelection, // Destructure the new prop checkboxSelection, // Destructure the new prop
rowSelectionModel,
onRowSelectionModelChange, // Destructure the new prop onRowSelectionModelChange, // Destructure the new prop
selectionModel, selectionModel,
onRowClick, onRowClick,
@@ -200,6 +202,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
getRowHeight={() => 'auto'} getRowHeight={() => 'auto'}
onRowClick={onRowClick} onRowClick={onRowClick}
checkboxSelection={checkboxSelection} checkboxSelection={checkboxSelection}
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={onRowSelectionModelChange} onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }} experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel} columnGroupingModel={columnGroupingModel}
@@ -233,6 +236,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
getRowHeight={() => 'auto'} getRowHeight={() => 'auto'}
onRowClick={onRowClick} onRowClick={onRowClick}
checkboxSelection={checkboxSelection} checkboxSelection={checkboxSelection}
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={onRowSelectionModelChange} onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }} experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel} columnGroupingModel={columnGroupingModel}
@@ -266,6 +270,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
getRowHeight={() => 'auto'} getRowHeight={() => 'auto'}
onRowClick={onRowClick} onRowClick={onRowClick}
checkboxSelection={checkboxSelection} checkboxSelection={checkboxSelection}
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={onRowSelectionModelChange} onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }} experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel} columnGroupingModel={columnGroupingModel}
@@ -301,6 +306,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({
onRowClick={onRowClick} onRowClick={onRowClick}
style={{ marginRight: 0 }} style={{ marginRight: 0 }}
checkboxSelection={checkboxSelection} checkboxSelection={checkboxSelection}
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={onRowSelectionModelChange} onRowSelectionModelChange={onRowSelectionModelChange}
experimentalFeatures={{ columnGrouping: true }} experimentalFeatures={{ columnGrouping: true }}
columnGroupingModel={columnGroupingModel} columnGroupingModel={columnGroupingModel}


+ 11
- 5
src/components/NavigationContent/NavigationContent.tsx 查看文件

@@ -117,11 +117,11 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => {
abilities!.includes(ability), abilities!.includes(ability),
), ),
children: [ children: [
{
icon: <SummarizeIcon />,
label: "Financial Summary",
path: "/dashboard/ProjectFinancialSummary",
},
// {
// icon: <SummarizeIcon />,
// label: "Financial Summary",
// path: "/dashboard/ProjectFinancialSummary",
// },
{ {
icon: <PaymentsIcon />, icon: <PaymentsIcon />,
label: "Company / Team Cash Flow", label: "Company / Team Cash Flow",
@@ -162,6 +162,12 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => {
}, },
], ],
}, },
{
icon: <SummarizeIcon />,
label: "Financial Summary",
path: "/dashboard/ProjectFinancialSummary",
showOnMobile: true,
},
// No Claim function in Breaur, will be implement later // No Claim function in Breaur, will be implement later
// { // {
// icon: <RequestQuote />, // icon: <RequestQuote />,


+ 35
- 2
src/components/ProgressByClient/ProgressByClient.tsx 查看文件

@@ -25,6 +25,9 @@ import { useSearchParams } from 'next/navigation';
import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions"; import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions";
// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false });


type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>;
type SearchProjectParamNames = keyof SearchProjectQuery;

interface Props { interface Props {
// clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; // clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[];
} }
@@ -51,6 +54,7 @@ const ProgressByClient: React.FC<Props> = () => {
React.useState("-"); React.useState("-");
const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-");
const [remainedManhour, setRemainedManhour]: any = React.useState("-"); const [remainedManhour, setRemainedManhour]: any = React.useState("-");
const [filteredClientSubsidiaryProjectResult, setFilteredClientSubsidiaryProjectResult]:any[] = useState([]);
const [lastUpdate, setLastUpdate]: any = React.useState("-"); const [lastUpdate, setLastUpdate]: any = React.useState("-");
const [dropdownDemo, setDropdownDemo] = useState(""); const [dropdownDemo, setDropdownDemo] = useState("");
const [dateDemo, setDateDemo] = useState(null); const [dateDemo, setDateDemo] = useState(null);
@@ -100,6 +104,7 @@ const ProgressByClient: React.FC<Props> = () => {
Number(customerId),tableSorting,Number(0)) Number(customerId),tableSorting,Number(0))
console.log(clickResult) console.log(clickResult)
setClientSubsidiaryProjectResult(clickResult); setClientSubsidiaryProjectResult(clickResult);
setFilteredClientSubsidiaryProjectResult(clickResult);
} else { } else {
const clickResult = await fetchAllClientSubsidiaryProjects( const clickResult = await fetchAllClientSubsidiaryProjects(
Number(customerId), Number(customerId),
@@ -110,6 +115,14 @@ const ProgressByClient: React.FC<Props> = () => {
} }
} catch (error) {
console.error('Error fetching client subsidiary projects:', error);
}
} else if (customerId) {
try {
const clickResult = await fetchAllClientSubsidiaryProjects(
Number(customerId),tableSorting)
setClientSubsidiaryProjectResult(clickResult);
} catch (error) { } catch (error) {
console.error('Error fetching client subsidiary projects:', error); console.error('Error fetching client subsidiary projects:', error);
} }
@@ -141,7 +154,13 @@ const ProgressByClient: React.FC<Props> = () => {
fetchData() fetchData()
}, [customerId,subsidiaryId,tableSorting]); }, [customerId,subsidiaryId,tableSorting]);



const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo(
() => [
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
{ label: t("Project Name"), paramName: "projectName", type: "text" },
],
[t],
);


const rows2 = [ const rows2 = [
{ {
@@ -626,11 +645,25 @@ const ProgressByClient: React.FC<Props> = () => {
className="text-slate-500" className="text-slate-500"
title= {t("Resource Consumption and Coming Milestones")} title= {t("Resource Consumption and Coming Milestones")}
/> />
{clientSubsidiaryProjectResult.length > 0 && (
<SearchBox
criteria={projectSearchCriteria}
onSearch={(query) => {
setFilteredClientSubsidiaryProjectResult(
clientSubsidiaryProjectResult.filter(
(cp:any) =>
cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
cp.projectName.toLowerCase().includes(query.projectName.toLowerCase())
),
);
}}
/>
)}
<div <div
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} style={{ display: "inline-block", width: "99%", marginLeft: 10 }}
> >
<CustomDatagrid <CustomDatagrid
rows={clientSubsidiaryProjectResult}
rows={filteredClientSubsidiaryProjectResult}
columns={columns2} columns={columns2}
columnWidth={200} columnWidth={200}
dataGridHeight={300} dataGridHeight={300}


+ 29
- 2
src/components/ProgressByTeam/ProgressByTeam.tsx 查看文件

@@ -19,9 +19,12 @@ import SearchBox, { Criterion } from "../SearchBox";
import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; import ProgressByTeamSearch from "@/components/ProgressByTeamSearch";
import { Suspense } from "react"; import { Suspense } from "react";
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { fetchAllTeamProjects} from "@/app/api/teamprojects/actions";
import { fetchAllTeamProjects,ClientSubsidiaryProjectResult} from "@/app/api/teamprojects/actions";
// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false });


type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>;
type SearchProjectParamNames = keyof SearchProjectQuery;

const ProgressByTeam: React.FC = () => { const ProgressByTeam: React.FC = () => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const teamLeadId = searchParams.get('teamLeadId'); const teamLeadId = searchParams.get('teamLeadId');
@@ -51,6 +54,7 @@ const ProgressByTeam: React.FC = () => {
const [chartProjectName, setChartProjectName]:any[] = useState([]); const [chartProjectName, setChartProjectName]:any[] = useState([]);
const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]);
const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]);
const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]);
const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]);
const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]);
const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b",
@@ -89,6 +93,14 @@ const ProgressByTeam: React.FC = () => {
const recordsPerPage = 10; const recordsPerPage = 10;
const [tableSorting, setTableSorting] = useState('ProjectName'); const [tableSorting, setTableSorting] = useState('ProjectName');


const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo(
() => [
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
{ label: t("Project Name"), paramName: "projectName", type: "text" },
],
[t],
);

const fetchData = async () => { const fetchData = async () => {
console.log(tableSorting) console.log(tableSorting)
if (teamLeadId) { if (teamLeadId) {
@@ -97,6 +109,7 @@ const ProgressByTeam: React.FC = () => {
Number(teamLeadId),tableSorting) Number(teamLeadId),tableSorting)
console.log(clickResult) console.log(clickResult)
setTeamProjectResult(clickResult); setTeamProjectResult(clickResult);
setFilteredTeamProjectResult(clickResult);
} catch (error) { } catch (error) {
console.error('Error fetching team projects:', error); console.error('Error fetching team projects:', error);
} }
@@ -742,11 +755,25 @@ const ProgressByTeam: React.FC = () => {
className="text-slate-500" className="text-slate-500"
title= {t("Resource Consumption and Coming Milestones")} title= {t("Resource Consumption and Coming Milestones")}
/> />
{teamProjectResult.length > 0 && (
<SearchBox
criteria={projectSearchCriteria}
onSearch={(query) => {
setFilteredTeamProjectResult(
teamProjectResult.filter(
(cp:any) =>
cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
cp.projectName.toLowerCase().includes(query.projectName.toLowerCase())
),
);
}}
/>
)}
<div <div
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} style={{ display: "inline-block", width: "99%", marginLeft: 10 }}
> >
<CustomDatagrid <CustomDatagrid
rows={teamProjectResult}
rows={filteredTeamProjectResult}
columns={columns2} columns={columns2}
columnWidth={200} columnWidth={200}
dataGridHeight={300} dataGridHeight={300}


+ 31
- 24
src/components/ProjectCashFlow/ProjectCashFlow.tsx 查看文件

@@ -25,6 +25,7 @@ import { CashFlow } from "@/app/api/cashflow";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { useSearchParams } from 'next/navigation';


interface Props { interface Props {
projects: CashFlow[]; projects: CashFlow[];
@@ -34,8 +35,10 @@ type SearchParamNames = keyof SearchQuery;


const ProjectCashFlow: React.FC = () => { const ProjectCashFlow: React.FC = () => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const searchParams = useSearchParams();
const projectId = searchParams.get('projectId');
const todayDate = new Date(); const todayDate = new Date();
const [selectionModel, setSelectionModel]: any[] = React.useState([]);
const [selectionModel, setSelectionModel]: any[] = useState<GridRowSelectionModel>([]);
const [projectData, setProjectData]: any[] = React.useState([]); const [projectData, setProjectData]: any[] = React.useState([]);
const [filteredResult, setFilteredResult]:any[] = useState([]); const [filteredResult, setFilteredResult]:any[] = useState([]);
const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]); const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]);
@@ -59,6 +62,7 @@ const ProjectCashFlow: React.FC = () => {
const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]);
const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]);
const [ledgerData, setLedgerData]: any[] = React.useState([]); const [ledgerData, setLedgerData]: any[] = React.useState([]);
const [isInitializing, setIsInitializing] = useState(true);
const [cashFlowYear, setCashFlowYear]: any[] = React.useState( const [cashFlowYear, setCashFlowYear]: any[] = React.useState(
todayDate.getFullYear(), todayDate.getFullYear(),
); );
@@ -67,25 +71,24 @@ const ProjectCashFlow: React.FC = () => {
); );


const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => {
const selectedRowsData = projectData.filter((row: any) =>
newSelectionModel.includes(row.id),
);
const projectIdList = []
for (var i=0; i<selectedRowsData.length; i++){
projectIdList.push(selectedRowsData[i].id)
if (!isInitializing) {
setSelectionModel(newSelectionModel);
const selectedRowsData = projectData.filter((row: any) =>
newSelectionModel.includes(row.id)
);
const projectIdList = selectedRowsData.map((row: any) => row.id);
setSelectedProjectIdList(projectIdList);
} }
setSelectedProjectIdList(projectIdList)
}; };


const fetchData = async () => { const fetchData = async () => {
const cashFlowProject = await fetchProjectsCashFlow(); const cashFlowProject = await fetchProjectsCashFlow();
console.log(cashFlowProject)
setProjectData(cashFlowProject) setProjectData(cashFlowProject)
setFilteredResult(cashFlowProject) setFilteredResult(cashFlowProject)
} }
const fetchChartData = async () => { const fetchChartData = async () => {
const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear);
console.log(cashFlowMonthlyChartData)
const monthlyIncome = [] const monthlyIncome = []
const cumulativeIncome = [] const cumulativeIncome = []
const monthlyExpenditure = [] const monthlyExpenditure = []
@@ -165,8 +168,7 @@ const ProjectCashFlow: React.FC = () => {
setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0])
setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0])
} }
console.log(cashFlowAnticipateData)
if(cashFlowAnticipateData.length !== 0){ if(cashFlowAnticipateData.length !== 0){
if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) { if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) {
const anticipateExpenditureList = [] const anticipateExpenditureList = []
@@ -186,7 +188,6 @@ const ProjectCashFlow: React.FC = () => {
} }
anticipateExpenditureList.push(subAnticipateExpenditure) anticipateExpenditureList.push(subAnticipateExpenditure)
} }
console.log(anticipateExpenditureList)
const result = new Array(anticipateExpenditureList[0].length).fill(0); const result = new Array(anticipateExpenditureList[0].length).fill(0);
for (const arr of anticipateExpenditureList) { for (const arr of anticipateExpenditureList) {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
@@ -211,18 +212,28 @@ const ProjectCashFlow: React.FC = () => {
setLedgerData(cashFlowLedgerData) setLedgerData(cashFlowLedgerData)
} }
useEffect(() => { useEffect(() => {
fetchData()
if (projectId !== null) {
setSelectedProjectIdList([parseInt(projectId)])
setSelectionModel([parseInt(projectId)]);
}
fetchData().then(() => {
setIsInitializing(false);
});
}, []); }, []);


useEffect(() => { useEffect(() => {
fetchChartData()
fetchReceivableAndExpenditureData()
fetchAnticipateData()
fetchProjectCashFlowLedger()
fetchChartData();
fetchReceivableAndExpenditureData();
fetchAnticipateData();
fetchProjectCashFlowLedger();
}, [cashFlowYear,selectedProjectIdList]); }, [cashFlowYear,selectedProjectIdList]);

useEffect(() => { useEffect(() => {
fetchAnticipateData()
fetchAnticipateData();
}, [anticipateCashFlowYear,selectedProjectIdList]); }, [anticipateCashFlowYear,selectedProjectIdList]);

const columns = [ const columns = [
{ {
id: "projectCode", id: "projectCode",
@@ -751,15 +762,12 @@ const ProjectCashFlow: React.FC = () => {
); );


function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean { function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean {
console.log(startDate)
console.log(endDate)
if (!startDate || !endDate) { if (!startDate || !endDate) {
return false; return false;
} }
const dateToCheckObj = new Date(dateToCheck); const dateToCheckObj = new Date(dateToCheck);
const startDateObj = new Date(startDate); const startDateObj = new Date(startDate);
const endDateObj = new Date(endDate); const endDateObj = new Date(endDate);
console.log(dateToCheckObj)
return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj; return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj;
} }


@@ -774,7 +782,6 @@ const ProjectCashFlow: React.FC = () => {
<SearchBox <SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={(query) => { onSearch={(query) => {
console.log(query)
setFilteredResult( setFilteredResult(
projectData.filter( projectData.filter(
(cp:any) => (cp:any) =>
@@ -794,7 +801,7 @@ const ProjectCashFlow: React.FC = () => {
dataGridHeight={300} dataGridHeight={300}
checkboxSelection={true} checkboxSelection={true}
onRowSelectionModelChange={handleSelectionChange} onRowSelectionModelChange={handleSelectionChange}
selectionModel={selectionModel}
rowSelectionModel={selectionModel}
/> />
<Grid item sm> <Grid item sm>
<div style={{ display: "inline-block", width: "50%" }}> <div style={{ display: "inline-block", width: "50%" }}>


+ 167
- 41
src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx 查看文件

@@ -17,9 +17,13 @@ import { AnyARecord, AnyCnameRecord } from "dns";
import SearchBox, { Criterion } from "../SearchBox"; import SearchBox, { Criterion } from "../SearchBox";
import ProgressByClientSearch from "@/components/ProgressByClientSearch"; import ProgressByClientSearch from "@/components/ProgressByClientSearch";
import { Suspense } from "react"; import { Suspense } from "react";
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked';
import { useRouter } from "next/navigation";


interface Props { interface Props {
Title: string; Title: string;
TeamId: number;
TotalActiveProjectNumber: number; TotalActiveProjectNumber: number;
TotalFees: number; TotalFees: number;
TotalBudget: number; TotalBudget: number;
@@ -41,6 +45,7 @@ const dataPositiveStyle:any = { color: "#71d19e", textAlign: "right" }


const ProjectFinancialCard: React.FC<Props> = ({ const ProjectFinancialCard: React.FC<Props> = ({
Title, Title,
TeamId,
TotalActiveProjectNumber, TotalActiveProjectNumber,
TotalFees, TotalFees,
TotalBudget, TotalBudget,
@@ -55,8 +60,12 @@ const ProjectFinancialCard: React.FC<Props> = ({
ProjectedCashFlowStatus, ProjectedCashFlowStatus,
Index, Index,
}) => { }) => {
const router = useRouter();
const [SearchCriteria, setSearchCriteria] = React.useState({}); const [SearchCriteria, setSearchCriteria] = React.useState({});
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const handleCheckProjectStatusClick = (TeamId:number) => {
router.push(`/dashboard/ProjectStatusByTeam?teamLeadId=${TeamId}`);
};
const borderColor = const borderColor =
CashFlowStatus === "Negative" CashFlowStatus === "Negative"
? "border-red-300 border-solid" ? "border-red-300 border-solid"
@@ -75,116 +84,233 @@ const ProjectFinancialCard: React.FC<Props> = ({
className={`${borderColor}`} className={`${borderColor}`}
> >
<div <div
className="text-xl mt-2 font-medium"
className="text-xl mt-2 font-medium inline-block"
style={{ width: "100%", textAlign: "center", color: "#898d8d" }} style={{ width: "100%", textAlign: "center", color: "#898d8d" }}
> >
{Title}
{Title !== t("All Team") ?
<div className="ml-10 inline-block">{Title}</div>
:
<div className="ml-10 inline-block mb-7">{Title}</div>
}
{ClickedIndex === Index ?
<div className="inline-block float-right mt-1 mr-5 text-gray-500"><RadioButtonCheckedIcon /></div>
:
<div className="inline-block float-right mt-1 mr-5 text-gray-400"><RadioButtonUncheckedIcon /></div>
}
</div> </div>
<hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Active Project")}
{Title !== t("All Team") ?
<>
<div onClick={(r) => handleCheckProjectStatusClick(TeamId)} className="bg-lime-100 mb-2 border-b-2 pt-1 pb-1 hover:bg-lime-200" style={{ width: "100%", textAlign: "center", color: "#898d8d" }}>{t("Check Project Status")}</div>
</>
:
<><hr /></>
}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(a) " + t("Total Active Project")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalActiveProjectNumber.toLocaleString()} {TotalActiveProjectNumber.toLocaleString()}
</div> </div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Fees")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(b) " + t("Total Fees")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Budget")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(c) " + t("Total Budget")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"(c) = (b) * 80%"}</div>
</div>
</div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Cumulative Expenditure")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(d) " + t("Total Cumulative Expenditure")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Invoiced Amount")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(e) " + t("Total Invoiced Amount")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Un-Invoiced Amount")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(f) " + t("Total Un-Invoiced Amount")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2">{"(f) =(b) - (e)"}</div>
</div>
</div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Total Received Amount")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(g) " + t("Total Received Amount")}
</div> </div>
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<hr /> <hr />
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Cash Flow Status")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(h) " + t("Cash Flow Status")}
</div> </div>
{CashFlowStatus === "Negative" && (
<>
<div
className="text-lg font-medium mx-5"
style={dataNegativeStyle}
>
{t(CashFlowStatus)}
</div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (e) > or = (d)"}</div>
<div className="ml-2 mr-2 ">{"Negative: (e) < (d)"}</div>
</div>
</div>
<hr />
</>
)}
{CashFlowStatus === "Positive" && (
<> <>
<div <div
className="text-lg font-medium mx-5" className="text-lg font-medium mx-5"
style={CashFlowStatus === "Negative" && dataNegativeStyle || dataPositiveStyle}
style={dataPositiveStyle}
> >
{t(CashFlowStatus)} {t(CashFlowStatus)}
</div> </div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (e) > or = (d)"}</div>
<div className="ml-2 mr-2 ">{"Negative: (e) < (d)"}</div>
</div>
</div>
<hr /> <hr />
</> </>
)}
<div <div
className="text-sm mt-2 font-medium mx-5" className="text-sm mt-2 font-medium mx-5"
style={{ color: "#898d8d" }} style={{ color: "#898d8d" }}
> >
{t("Cost Performance Index") + " (CPI)"}
{"(i) " + t("Cost Performance Index") + " (CPI)"}
</div> </div>
{ ( { (
<> <>
<div <div
className="text-lg font-medium mx-5 mb-2" className="text-lg font-medium mx-5 mb-2"
style={Number(CostPerformanceIndex) < 1 && dataNegativeStyle || dataPositiveStyle}
style={dataNegativeStyle}
>
{CostPerformanceIndex}
</div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"(i) = (e) / (d)"}</div>
</div>
</div>
<hr />
</>
)}
{Number(CostPerformanceIndex) >= 1 && (
<>
<div
className="text-lg font-medium ml-5 mb-2"
style={dataPositiveStyle}
> >
{CostPerformanceIndex} {CostPerformanceIndex}
</div> </div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"(i) =(e) / (d)"}</div>
</div>
</div>
<hr /> <hr />
</> </>
)} )}
<div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}>
{t("Projected Cash Flow Status")}
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(j) " + t("Projected Cash Flow Status")}
</div> </div>
<>
<div
className="text-lg font-medium mx-5"
style={ProjectedCashFlowStatus === "Negative" && dataNegativeStyle || dataPositiveStyle}
>
{t(ProjectedCashFlowStatus)}
</div>
<hr />
</>
{ProjectedCashFlowStatus === "Negative" && (
<>
<div
className="text-lg font-medium mx-5"
style={dataNegativeStyle}
>
{t(ProjectedCashFlowStatus)}
</div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium mr-2 border-solid w-fit rounded-md mt-2 float-right" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (b) > or = (d)"}</div>
<div className="ml-2 mr-2 ">{"Negative: (b) < (d)"}</div>
</div>
</div>
<hr />
</>
)}
{ProjectedCashFlowStatus === "Positive" && (
<>
<div
className="text-lg font-medium mx-5"
style={dataPositiveStyle}
>
{t(ProjectedCashFlowStatus)}
</div>
<div style={{ overflow: 'hidden' }}>
<div className="text-sm font-medium mr-2 border-solid w-fit rounded-md mt-2 float-right" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (b) > or = (d)"}</div>
<div className="ml-2 mr-2 ">{"Negative: (b) < (d)"}</div>
</div>
</div>
<hr />
</>
)}
<div <div
className="text-sm mt-2 font-medium mx-5"
className="text-sm mt-2 font-medium ml-5"
style={{ color: "#898d8d" }} style={{ color: "#898d8d" }}
> >
{t("Projected Cost Performance Index") + " (CPI)"}
{"(k) " + t("Projected Cost Performance Index") + " (CPI)"}
</div> </div>
{Number(ProjectedCPI) < 1 && (
<> <>
<div
className="text-lg font-medium mx-5 mb-2"
style={Number(ProjectedCPI) < 1 && dataNegativeStyle || dataPositiveStyle}
>
{ProjectedCPI}
</div>
<div
className="text-lg font-medium mx-5 mb-2"
style={dataNegativeStyle}
>
{ProjectedCPI}
</div>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div>
</div>
</> </>
)}
{Number(ProjectedCPI) >= 1 && (
<>
<div
className="text-lg font-medium mx-5 mb-2"
style={dataPositiveStyle}
>
{ProjectedCPI}
</div>
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}>
<div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div>
</div>
</>
)}
</Card> </Card>
); );
}; };


+ 24
- 2
src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx 查看文件

@@ -24,6 +24,7 @@ import ProjectFinancialCard from "./ProjectFinancialCard";
import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityIcon from '@mui/icons-material/Visibility';
import { downloadFile } from "@/app/utils/commonUtil"; import { downloadFile } from "@/app/utils/commonUtil";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { useRouter } from "next/navigation";


type SearchProjectQuery = Partial<Omit<FinancialSummaryByProjectResult, "id">>; type SearchProjectQuery = Partial<Omit<FinancialSummaryByProjectResult, "id">>;
type SearchClientQuery = Partial<Omit<FinancialSummaryByClientResult, "id">>; type SearchClientQuery = Partial<Omit<FinancialSummaryByClientResult, "id">>;
@@ -31,6 +32,7 @@ type SearchProjectParamNames = keyof SearchProjectQuery;
type SearchClientParamNames = keyof SearchClientQuery; type SearchClientParamNames = keyof SearchClientQuery;


const ProjectFinancialSummary: React.FC = () => { const ProjectFinancialSummary: React.FC = () => {
const router = useRouter();
const [SearchCriteria, setSearchCriteria] = React.useState({}); const [SearchCriteria, setSearchCriteria] = React.useState({});
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const [selectionModel, setSelectionModel]: any[] = React.useState([]); const [selectionModel, setSelectionModel]: any[] = React.useState([]);
@@ -111,7 +113,17 @@ const ProjectFinancialSummary: React.FC = () => {
id: 'customerCode', id: 'customerCode',
field: 'customerCode', field: 'customerCode',
headerName: t("Client Code"), headerName: t("Client Code"),
minWidth:50
minWidth:50,
renderCell: (params: any) => (
<div
className="text-blue-600 hover:underline cursor-pointer"
onClick={() => {
router.push(`/dashboard/ProjectStatusByClient?customerId=${params.row.id}`);
}}
>
{params.value}
</div>
),
}, },
{ {
id: 'customerName', id: 'customerName',
@@ -323,6 +335,16 @@ const columns2 = [
field: 'projectCode', field: 'projectCode',
headerName: t("Project Code"), headerName: t("Project Code"),
minWidth:50, minWidth:50,
renderCell: (params: any) => (
<div
className="text-blue-600 hover:underline cursor-pointer"
onClick={() => {
router.push(`/dashboard/ProjectCashFlow?projectId=${params.row.id}`);
}}
>
{params.value}
</div>
),
}, },
{ {
id: 'projectName', id: 'projectName',
@@ -524,7 +546,7 @@ const columns2 = [
<div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}>
{projectFinancialData.map((record:any, index:any) => ( {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,index)}> <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record,index)}>
<ProjectFinancialCard Title={record.teamName == "All Team" ? t("All Team") : record.teamName} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure ?? 0} TotalInvoicedAmount={record.totalInvoiced ?? 0} TotalUnInvoicedAmount={record.unInvoiced ?? 0} TotalReceivedAmount={record.totalReceived ?? 0} CashFlowStatus={record.cashFlowStatus ?? "Negative"} CostPerformanceIndex={record.cpi ?? 0} ProjectedCashFlowStatus={record.projectedCashFlowStatus ?? "Negative"} ProjectedCPI={record.projectedCpi ?? 0} ClickedIndex={isCardClickedIndex} Index={index}/>
<ProjectFinancialCard Title={record.teamName == "All Team" ? t("All Team") : record.teamName} TeamId={record.teamId} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure ?? 0} TotalInvoicedAmount={record.totalInvoiced ?? 0} TotalUnInvoicedAmount={record.unInvoiced ?? 0} TotalReceivedAmount={record.totalReceived ?? 0} CashFlowStatus={record.cashFlowStatus ?? "Negative"} CostPerformanceIndex={record.cpi ?? 0} ProjectedCashFlowStatus={record.projectedCashFlowStatus ?? "Negative"} ProjectedCPI={record.projectedCpi ?? 0} ClickedIndex={isCardClickedIndex} Index={index}/>
</div> </div>
))} ))}
</div> </div>


+ 28
- 3
src/components/ProjectResourceConsumptionRanking/ProjectResourceConsumptionRanking.tsx 查看文件

@@ -19,10 +19,11 @@ import SearchBox, { Criterion } from "../SearchBox";
import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; import ProgressByTeamSearch from "@/components/ProgressByTeamSearch";
import { Suspense } from "react"; import { Suspense } from "react";
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { fetchAllTeamProjects, TeamProjectResult, fetchTeamProjects, fetchAllTeamConsumption, fetchAllTeamConsumptionColorOrder} from "@/app/api/teamprojects/actions";
import { fetchAllTeamProjects, TeamProjectResult, ClientSubsidiaryProjectResult, fetchTeamProjects, fetchAllTeamConsumption, fetchAllTeamConsumptionColorOrder} from "@/app/api/teamprojects/actions";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false });

type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>;
type SearchProjectParamNames = keyof SearchProjectQuery;
interface Props { interface Props {
projects: TeamProjectResult[]; projects: TeamProjectResult[];
} }
@@ -44,6 +45,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => {
const [colorArray, setColorArray]: any[] = useState([]); const [colorArray, setColorArray]: any[] = useState([]);
const [selectionModel, setSelectionModel]: any[] = React.useState([]); const [selectionModel, setSelectionModel]: any[] = React.useState([]);
const [pieChartColor, setPieChartColor]: any[] = React.useState([]); const [pieChartColor, setPieChartColor]: any[] = React.useState([]);
const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]);
const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState();
const [projectBudgetManhour, setProjectBudgetManhour]: any = const [projectBudgetManhour, setProjectBudgetManhour]: any =
React.useState("-"); React.useState("-");
@@ -117,6 +119,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => {
selectedTeamIdList,tableSorting) selectedTeamIdList,tableSorting)
console.log(clickResult) console.log(clickResult)
setTeamProjectResult(clickResult); setTeamProjectResult(clickResult);
setFilteredTeamProjectResult(clickResult);
setTeamProjectColorOrder(colorOrder); setTeamProjectColorOrder(colorOrder);
} catch (error) { } catch (error) {
console.error('Error fetching team consumption:', error); console.error('Error fetching team consumption:', error);
@@ -124,6 +127,14 @@ const ProjectResourceConsumptionRanking: React.FC = () => {
} }
} }


const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo(
() => [
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
{ label: t("Project Name"), paramName: "projectName", type: "text" },
],
[t],
);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ label: t("Team Code"), paramName: "teamCode", type: "text" }, { label: t("Team Code"), paramName: "teamCode", type: "text" },
@@ -865,11 +876,25 @@ const ProjectResourceConsumptionRanking: React.FC = () => {
className="text-slate-500" className="text-slate-500"
title={t("Resource Consumption and Coming Milestones")} title={t("Resource Consumption and Coming Milestones")}
/> />
{teamProjectResult.length > 0 && (
<SearchBox
criteria={projectSearchCriteria}
onSearch={(query) => {
setFilteredTeamProjectResult(
teamProjectResult.filter(
(cp:any) =>
cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
cp.projectName.toLowerCase().includes(query.projectName.toLowerCase())
),
);
}}
/>
)}
<div <div
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} style={{ display: "inline-block", width: "99%", marginLeft: 10 }}
> >
<CustomDatagrid <CustomDatagrid
rows={teamProjectResult}
rows={filteredTeamProjectResult}
columns={columns2} columns={columns2}
columnWidth={200} columnWidth={200}
dataGridHeight={300} dataGridHeight={300}


+ 4
- 4
src/components/ProjectSearch/ProjectSearch.tsx 查看文件

@@ -28,16 +28,16 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories, abilities


const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ label: t("Project code"), paramName: "code", type: "text" },
{ label: t("Project name"), paramName: "name", type: "text" },
{ label: t("Project Code"), paramName: "code", type: "text" },
{ label: t("Project Name"), paramName: "name", type: "text" },
{ {
label: t("Client name"),
label: t("Client Name"),
paramName: "client", paramName: "client",
type: "autocomplete", type: "autocomplete",
options: uniqBy(projects.map((project) => ({value: project.client, label: project.client})), "value").sort((a, b) => a.value >= b.value ? 1 : -1), options: uniqBy(projects.map((project) => ({value: project.client, label: project.client})), "value").sort((a, b) => a.value >= b.value ? 1 : -1),
}, },
{ {
label: t("Project category"),
label: t("Project Category"),
paramName: "category", paramName: "category",
type: "select", type: "select",
options: projectCategories.map((category) => category.name), options: projectCategories.map((category) => category.name),


+ 2
- 1
src/i18n/en/dashboard.json 查看文件

@@ -155,5 +155,6 @@
"Stage": "Stage", "Stage": "Stage",
"Task Count": "Task Count", "Task Count": "Task Count",
"Total": "Total", "Total": "Total",
"Status": "Status"
"Status": "Status",
"Check Project Status": "Check Project Status"
} }

+ 16
- 1
src/i18n/en/projects.json 查看文件

@@ -1 +1,16 @@
{}
{
"Project Management": "Project Management",
"Create Sub Project": "Create Sub Project",
"Create Project": "Create Project",
"Project Code": "Project Code",
"Project Name": "Project Name",
"Client Name": "Client Name",
"Client": "Client",
"Project Category": "Project Category",
"Team": "Team",
"Status": "Status",
"Details": "Details",
"Awarded Project": "Awarded Project",
"Project to be bidded": "Project to be bidded",
"On-going": "On-going"
}

+ 2
- 1
src/i18n/zh/dashboard.json 查看文件

@@ -156,5 +156,6 @@
"Stage": "階段", "Stage": "階段",
"Task Count": "工作數量", "Task Count": "工作數量",
"Total": "總計", "Total": "總計",
"Status": "狀態"
"Status": "狀態",
"Check Project Status": "查看項目狀態"
} }

+ 16
- 1
src/i18n/zh/projects.json 查看文件

@@ -1 +1,16 @@
{}
{
"Project Management": "項目管理",
"Create Sub Project": "創建子項目",
"Create Project": "創建項目",
"Project Code": "項目代碼",
"Project Name": "項目名稱",
"Client Name": "客戶名稱",
"Client": "客戶",
"Project Category": "項目類別",
"Team": "團隊",
"Status": "狀態",
"Details": "詳細信息",
"Awarded Project": "已獲得的項目",
"Project to be bidded": "待投標項目",
"On-going": "進行中"
}

正在加载...
取消
保存