- {TotalActiveProjectNumber}
+ {TotalActiveProjectNumber.toLocaleString()}
- {TotalFees}
+ {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
- {TotalBudget}
+ {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
- {TotalCumulative}
+ {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
- {TotalInvoicedAmount}
+ {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
- {TotalReceivedAmount}
+ {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
diff --git a/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx
index e0a2764..c803b59 100644
--- a/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx
+++ b/src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx
@@ -18,86 +18,33 @@ import { AnyARecord, AnyCnameRecord } from "dns";
import SearchBox, { Criterion } from "../SearchBox";
import ProgressByClientSearch from "@/components/ProgressByClientSearch";
import { Suspense } from "react";
+import { fetchFinancialSummaryCard } from "@/app/api/financialsummary";
+import { searchFinancialSummaryByClient,searchFinancialSummaryByProject } from "@/app/api/financialsummary/actions";
import ProjectFinancialCard from "./ProjectFinancialCard";
+import VisibilityIcon from '@mui/icons-material/Visibility';
const ProjectFinancialSummary: React.FC = () => {
const [SearchCriteria, setSearchCriteria] = React.useState({});
const { t } = useTranslation("dashboard");
const [selectionModel, setSelectionModel]: any[] = React.useState([]);
- const projectFinancialData = [
- {
- id: 1,
- title: "All Teams",
- activeProject: "147",
- fees: "22,800,000.00",
- budget: "18,240,000.00",
- cumulativeExpenditure: "17,950,000.00",
- invoicedAmount: "18,240,000.00",
- receivedAmount: "10,900,000.00",
- cashFlowStatus: "Negative",
- CPI: "0.69",
- },
- {
- id: 2,
- title: "XXX Team",
- activeProject: "25",
- fees: "1,500,000.00",
- budget: "1,200,000.00",
- cumulativeExpenditure: "1,250,000.00",
- invoicedAmount: "900,000.00",
- receivedAmount: "650,000.00",
- cashFlowStatus: "Negative",
- CPI: "0.72",
- },
- {
- id: 3,
- title: "YYY Team",
- activeProject: "35",
- fees: "5,000,000.00",
- budget: "4,000,000.00",
- cumulativeExpenditure: "3,200,000.00",
- invoicedAmount: "3,500,000.00",
- receivedAmount: "3,500,000.00",
- cashFlowStatus: "Positive",
- CPI: "1.09",
- },
- {
- id: 4,
- title: "ZZZ Team",
- activeProject: "50",
- fees: "3,500,000.00",
- budget: "2,800,000.00",
- cumulativeExpenditure: "5,600,000.00",
- invoicedAmount: "2,500,000.00",
- receivedAmount: "2,200,000.00",
- cashFlowStatus: "Negative",
- CPI: "0.45",
- },
- {
- id: 5,
- title: "AAA Team",
- activeProject: "15",
- fees: "4,800,000.00",
- budget: "3,840,000.00",
- cumulativeExpenditure: "2,500,000.00",
- invoicedAmount: "1,500,000.00",
- receivedAmount: "750,000.00",
- cashFlowStatus: "Negative",
- CPI: "0.60",
- },
- {
- id: 6,
- title: "BBB Team",
- activeProject: "22",
- fees: "8,000,000.00",
- budget: "6,400,000.00",
- cumulativeExpenditure: "5,400,000.00",
- invoicedAmount: "4,000,000.00",
- receivedAmount: "3,800,000.00",
- cashFlowStatus: "Negative",
- CPI: "0.74",
- },
- ];
+ const [projectFinancialData, setProjectFinancialData]: any[] = React.useState([]);
+ const [clientFinancialRows, setClientFinancialRows]: any[] = React.useState([]);
+ const [projectFinancialRows, setProjectFinancialRows]: any[] = React.useState([]);
+ const fetchData = async () => {
+ const financialSummaryCard = await fetchFinancialSummaryCard();
+ setProjectFinancialData(financialSummaryCard)
+ }
+ const fetchTableData = async (teamId?:any) => {
+ const financialSummaryByClient = await searchFinancialSummaryByClient(teamId);
+
+ console.log(financialSummaryByClient)
+ // console.log(financialSummaryByProject)
+ setClientFinancialRows(financialSummaryByClient)
+ }
+ useEffect(() => {
+ fetchData()
+ fetchTableData(undefined)
+ }, []);
const rows0 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"},
{id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"},
@@ -115,45 +62,39 @@ const ProjectFinancialSummary: React.FC = () => {
{id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"}
]
- const projectFinancialRows = [{id: 1,projectCode:"M1354",projectName:"Consultanct Project BBB",clientName:"Client D",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}
- ]
+ // const projectFinancialRows = [{id: 1,projectCode:"M1354",projectName:"Consultanct Project BBB",clientName:"Client D",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}
+ // ]
- const clientFinancialRows =[{id: 1,clientCode:"Cust-02",clientName:"Client B",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"},
- {id: 2,clientCode:"Cust-03",clientName:"Client C",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"},
- {id: 3,clientCode:"Cust-04",clientName:"Client D",totalProjectInvolved:"4",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}
- ]
+ // const clientFinancialRows =[{id: 1,clientCode:"Cust-02",clientName:"Client B",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"},
+ // {id: 2,clientCode:"Cust-03",clientName:"Client C",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"},
+ // {id: 3,clientCode:"Cust-04",clientName:"Client D",totalProjectInvolved:"4",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}
+ // ]
const [isCardClickedIndex, setIsCardClickedIndex] = React.useState(0);
const [selectedTeamData, setSelectedTeamData]: any[] = React.useState(rows0);
- const handleCardClick = (r: any) => {
- setIsCardClickedIndex(r);
- if (r === 0) {
- setSelectedTeamData(rows0);
- } else if (r === 1) {
- setSelectedTeamData(rows1);
- } else if (r === 2) {
- setSelectedTeamData(rows2);
- }
+ const handleCardClick = (r: any, index:any) => {
+ fetchTableData(r.teamId)
+ setIsCardClickedIndex(index)
};
const columns = [
{
- id: 'clientCode',
- field: 'clientCode',
+ id: 'customerCode',
+ field: 'customerCode',
headerName: "Client Code",
flex: 0.7,
},
{
- id: 'clientName',
- field: 'clientName',
+ id: 'customerName',
+ field: 'customerName',
headerName: "Client Name",
flex: 1,
},
{
- id: 'totalProjectInvolved',
- field: 'totalProjectInvolved',
+ id: 'projectNo',
+ field: 'projectNo',
headerName: "Total Project Involved",
flex: 1,
},
@@ -163,6 +104,7 @@ const ProjectFinancialSummary: React.FC = () => {
headerName: "Cash Flow Status",
flex: 1,
renderCell: (params:any) => {
+ console.log(params.row)
if (params.row.cashFlowStatus === "Positive") {
return (
{params.row.cashFlowStatus}
@@ -192,13 +134,13 @@ const ProjectFinancialSummary: React.FC = () => {
},
},
{
- id: 'totalFees',
- field: 'totalFees',
+ id: 'totalFee',
+ field: 'totalFee',
headerName: "Total Fees (HKD)",
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalFees}
+
${params.row.totalFee}
)
},
},
@@ -214,46 +156,46 @@ const ProjectFinancialSummary: React.FC = () => {
},
},
{
- id: 'totalCumulativeExpenditure',
- field: 'totalCumulativeExpenditure',
+ id: 'cumulativeExpenditure',
+ field: 'cumulativeExpenditure',
headerName: "Total Cumulative Expenditure (HKD)",
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalCumulativeExpenditure}
+
${params.row.cumulativeExpenditure}
)
},
},
{
- id: 'totalInvoicedAmount',
- field: 'totalInvoicedAmount',
+ id: 'totalInvoiced',
+ field: 'totalInvoiced',
headerName: "Total Invoiced Amount (HKD)",
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalInvoicedAmount}
+
${params.row.totalInvoiced}
)
},
},
{
- id: 'totalUnInvoicedAmount',
- field: 'totalUnInvoicedAmount',
+ id: 'totalUnInvoiced',
+ field: 'totalUnInvoiced',
headerName: "Total Un-invoiced Amount (HKD)",
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalUnInvoicedAmount}
+
${params.row.totalUninvoiced}
)
},
},
{
- id: 'totalReceivedAmount',
- field: 'totalReceivedAmount',
+ id: 'totalReceived',
+ field: 'totalReceived',
headerName: "Total Received Amount (HKD)",
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalReceivedAmount}
+
${params.row.totalReceived}
)
},
},
@@ -322,8 +264,8 @@ const columns2 = [
flex: 1,
},
{
- id: 'clientName',
- field: 'clientName',
+ id: 'customerName',
+ field: 'customerName',
headerName: "Client Name",
flex: 1,
},
@@ -365,7 +307,7 @@ const columns2 = [
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalFees}
+
${params.row.totalFee}
)
},
},
@@ -387,7 +329,7 @@ const columns2 = [
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalCumulativeExpenditure}
+
${params.row.cumulativeExpenditure}
)
},
},
@@ -398,7 +340,7 @@ const columns2 = [
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalInvoicedAmount}
+
${params.row.totalInvoiced}
)
},
},
@@ -409,7 +351,7 @@ const columns2 = [
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalUnInvoicedAmount}
+
${params.row.totalUninvoiced}
)
},
},
@@ -420,7 +362,7 @@ const columns2 = [
flex: 1,
renderCell: (params:any) => {
return (
-
${params.row.totalReceivedAmount}
+
${params.row.totalReceived}
)
},
},
@@ -432,15 +374,25 @@ const columns2 = [
);
console.log(selectedRowsData);
};
+
+ const fetchProjectTableData = async (teamId?:any,customerId?:any) => {
+ const financialSummaryByProject = await searchFinancialSummaryByProject(teamId,customerId);
+ setProjectFinancialRows(financialSummaryByProject)
+ }
+
+ const handleRowClick = (params:any) => {
+ console.log(params.row.teamId);
+ fetchProjectTableData(params.row.teamId,params.row.cid)
+ };
return (
- {projectFinancialData.map((record, index) => (
-
handleCardClick(index)}>
-
+ {projectFinancialData.map((record:any, index:any) => (
+ handleCardClick(record,index)}>
+
))}
@@ -449,7 +401,7 @@ const columns2 = [
{/* */}
-
+
diff --git a/src/components/ProjectSearch/ProjectSearch.tsx b/src/components/ProjectSearch/ProjectSearch.tsx
index 79ee51b..6456902 100644
--- a/src/components/ProjectSearch/ProjectSearch.tsx
+++ b/src/components/ProjectSearch/ProjectSearch.tsx
@@ -20,7 +20,6 @@ type SearchParamNames = keyof SearchQuery;
const ProjectSearch: React.FC = ({ projects, projectCategories }) => {
const router = useRouter();
const { t } = useTranslation("projects");
- console.log(projects)
const [filteredProjects, setFilteredProjects] = useState(projects);
@@ -62,7 +61,9 @@ const ProjectSearch: React.FC = ({ projects, projectCategories }) => {
const onProjectClick = useCallback(
(project: ProjectResult) => {
- router.push(`/projects/edit?id=${project.id}`);
+ if (Boolean(project.mainProject)) {
+ router.push(`/projects/editSub?id=${project.id}`);
+ } else router.push(`/projects/edit?id=${project.id}`);
},
[router],
);
diff --git a/src/components/Report/ReportSearchBox3/SearchBox3.tsx b/src/components/Report/ReportSearchBox3/SearchBox3.tsx
deleted file mode 100644
index aafa7d0..0000000
--- a/src/components/Report/ReportSearchBox3/SearchBox3.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-//src\components\ReportSearchBox3\SearchBox3.tsx
-"use client";
-
-import Grid from "@mui/material/Grid";
-import Card from "@mui/material/Card";
-import CardContent from "@mui/material/CardContent";
-import Typography from "@mui/material/Typography";
-import React, { useCallback, useMemo, useState } from "react";
-import { useTranslation } from "react-i18next";
-import TextField from "@mui/material/TextField";
-import FormControl from "@mui/material/FormControl";
-import InputLabel from "@mui/material/InputLabel";
-import Select, { SelectChangeEvent } from "@mui/material/Select";
-import MenuItem from "@mui/material/MenuItem";
-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 dayjs from "dayjs";
-import "dayjs/locale/zh-hk";
-import { DatePicker } from "@mui/x-date-pickers/DatePicker";
-import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
-import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
-import { Box } from "@mui/material";
-import * as XLSX from 'xlsx-js-style';
-//import { DownloadReportButton } from '../LateStartReportGen/DownloadReportButton';
-
-interface BaseCriterion {
- label: string;
- label2?: string;
- paramName: T;
- paramName2?: T;
-}
-
-interface TextCriterion extends BaseCriterion {
- type: "text";
-}
-
-interface SelectCriterion extends BaseCriterion {
- type: "select";
- options: string[];
-}
-
-interface DateRangeCriterion extends BaseCriterion {
- type: "dateRange";
-}
-
-export type Criterion =
- | TextCriterion
- | SelectCriterion
- | DateRangeCriterion;
-
-interface Props {
- criteria: Criterion[];
- onSearch: (inputs: Record) => void;
- onReset?: () => void;
-}
-
-function SearchBox({
- criteria,
- onSearch,
- onReset,
-}: Props) {
- const { t } = useTranslation("common");
- const defaultInputs = useMemo(
- () =>
- criteria.reduce>(
- (acc, c) => {
- return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" };
- },
- {} as Record,
- ),
- [criteria],
- );
- const [inputs, setInputs] = useState(defaultInputs);
-
- const makeInputChangeHandler = useCallback(
- (paramName: T): React.ChangeEventHandler => {
- return (e) => {
- setInputs((i) => ({ ...i, [paramName]: e.target.value }));
- };
- },
- [],
- );
-
- const makeSelectChangeHandler = useCallback((paramName: T) => {
- return (e: SelectChangeEvent) => {
- setInputs((i) => ({ ...i, [paramName]: e.target.value }));
- };
- }, []);
-
- const makeDateChangeHandler = useCallback((paramName: T) => {
- return (e: any) => {
- setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
- };
- }, []);
-
- const makeDateToChangeHandler = useCallback((paramName: T) => {
- return (e: any) => {
- setInputs((i) => ({
- ...i,
- [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"),
- }));
- };
- }, []);
-
- const handleReset = () => {
- setInputs(defaultInputs);
- onReset?.();
- };
-
- const handleSearch = () => {
- onSearch(inputs);
-
- };
-
- const handleDownload = async () => {
- //setIsLoading(true);
-
- try {
- const response = await fetch('/temp/AR03_Resource Overconsumption.xlsx', {
- headers: {
- 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- },
- });
- if (!response.ok) throw new Error('Network response was not ok.');
-
- const data = await response.blob();
- const reader = new FileReader();
- reader.onload = (e) => {
- if (e.target && e.target.result) {
- const ab = e.target.result as ArrayBuffer;
- const workbook = XLSX.read(ab, { type: 'array' });
- const firstSheetName = workbook.SheetNames[0];
- const worksheet = workbook.Sheets[firstSheetName];
-
- // Add the current date to cell C2
- const cellAddress = 'C2';
- const date = new Date().toISOString().split('T')[0]; // Format YYYY-MM-DD
- const formattedDate = date.replace(/-/g, '/'); // Change format to YYYY/MM/DD
- XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: cellAddress });
-
- // Style for cell A1: Font size 16 and bold
- if (worksheet['A1']) {
- worksheet['A1'].s = {
- font: {
- bold: true,
- sz: 16, // Font size 16
- //name: 'Times New Roman' // Specify font
- }
- };
- }
-
- // Apply styles from A2 to A4 (bold)
- ['A2', 'A3', 'A4'].forEach(cell => {
- if (worksheet[cell]) {
- worksheet[cell].s = { font: { bold: true } };
- }
- });
-
- // Formatting from A6 to L6
- // Apply styles from A6 to L6 (bold, bottom border, center alignment)
- for (let col = 0; col < 12; col++) { // Columns A to K
- const cellRef = XLSX.utils.encode_col(col) + '6';
- if (worksheet[cellRef]) {
- worksheet[cellRef].s = {
- font: { bold: true },
- alignment: { horizontal: 'center' },
- border: {
- bottom: { style: 'thin', color: { auto: 1 } }
- }
- };
- }
- }
-
- const firstTableData = [
- ['Column1', 'Column2', 'Column3'], // Row 1
- ['Data1', 'Data2', 'Data3'], // Row 2
- // ... more rows as needed
- ];
- // Find the last row of the first table
- let lastRowOfFirstTable = 6; // Starting row for data in the first table
- while (worksheet[XLSX.utils.encode_cell({ c: 0, r: lastRowOfFirstTable })]) {
- lastRowOfFirstTable++;
- }
-
- // Calculate the maximum length of content in each column and set column width
- const colWidths: number[] = [];
-
- const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][];
- jsonData.forEach((row: (string | number)[]) => {
- row.forEach((cell: string | number, index: number) => {
- const valueLength = cell.toString().length;
- colWidths[index] = Math.max(colWidths[index] || 0, valueLength);
- });
- });
-
- // Apply calculated widths to each column, skipping column A
- worksheet['!cols'] = colWidths.map((width, index) => {
- if (index === 0) {
- return { wch: 8 }; // Set default or specific width for column A if needed
- }
- return { wch: width + 2 }; // Add padding to width
- });
-
- // Format filename with date
- const today = new Date().toISOString().split('T')[0].replace(/-/g, '_'); // Get current date and format as YYYY_MM_DD
- const filename = `AR03_Resource_Overconsumption_${today}.xlsx`; // Append formatted date to the filename
-
- // Convert workbook back to XLSX file
- XLSX.writeFile(workbook, filename);
- } else {
- throw new Error('Failed to load file');
- }
- };
- reader.readAsArrayBuffer(data);
- } catch (error) {
- console.error('Error downloading the file: ', error);
- }
-
- //setIsLoading(false);
- };
- return (
-
-
- {t("Search Criteria")}
-
- {criteria.map((c) => {
- return (
-
- {c.type === "text" && (
-
- )}
- {c.type === "select" && (
-
- {c.label}
-
-
- )}
- {c.type === "dateRange" && (
-
-
-
-
-
-
- {"-"}
-
-
-
-
-
-
- )}
-
- );
- })}
-
-
- }
- onClick={handleReset}
- >
- {t("Reset")}
-
- }
- onClick={handleDownload}
- >
- {t("Download")}
-
-
-
-
- );
-}
-
-export default SearchBox;
diff --git a/src/components/Report/ReportSearchBox3/index.ts b/src/components/Report/ReportSearchBox3/index.ts
deleted file mode 100644
index d481fbd..0000000
--- a/src/components/Report/ReportSearchBox3/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-//src\components\SearchBox\index.ts
-export { default } from "./SearchBox3";
-export type { Criterion } from "./SearchBox3";
diff --git a/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx b/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx
deleted file mode 100644
index 345b2f2..0000000
--- a/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-//src\components\DelayReport\DelayReport.tsx
-"use client";
-import * as React from "react";
-import "../../../app/global.css";
-import { Suspense } from "react";
-import ResourceOverconsumptionReportGen from "@/components/Report/ResourceOverconsumptionReportGen";
-
-const ResourceOverconsumptionReport: React.FC = () => {
-
- return (
- }>
-
-
- );
-};
-
-export default ResourceOverconsumptionReport;
\ No newline at end of file
diff --git a/src/components/Report/ResourceOverconsumptionReport/index.ts b/src/components/Report/ResourceOverconsumptionReport/index.ts
deleted file mode 100644
index ce20324..0000000
--- a/src/components/Report/ResourceOverconsumptionReport/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-//src\components\LateStartReport\index.ts
-export { default } from "./ResourceOverconsumptionReport";
diff --git a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx
deleted file mode 100644
index a6ec216..0000000
--- a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-//src\components\LateStartReportGen\LateStartReportGen.tsx
-"use client";
-import React, { useMemo, useState } from "react";
-import SearchBox, { Criterion } from "../ReportSearchBox3";
-import { useTranslation } from "react-i18next";
-import { ResourceOverconsumption } from "@/app/api/report3";
-
-interface Props {
- projects: ResourceOverconsumption[];
-}
-type SearchQuery = Partial>;
-type SearchParamNames = keyof SearchQuery;
-
-const ProgressByClientSearch: React.FC = ({ projects }) => {
- const { t } = useTranslation("projects");
-
- const searchCriteria: Criterion[] = useMemo(
- () => [
- { label: "Team", paramName: "team", type: "select", options: ["AAA", "BBB", "CCC"] },
- { label: "Client", paramName: "client", type: "select", options: ["Cust A", "Cust B", "Cust C"] },
- { label: "Status", paramName: "status", type: "select", options: ["Overconsumption", "Potential Overconsumption"] },
- // {
- // label: "Status",
- // label2: "Remained Date To",
- // paramName: "targetEndDate",
- // type: "dateRange",
- // },
- ],
- [t],
- );
-
- return (
- <>
- {
- console.log(query);
- }}
- />
- {/* */}
- >
- );
-};
-
-export default ProgressByClientSearch;
diff --git a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx
deleted file mode 100644
index a93f64b..0000000
--- a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx
-import { fetchProjectsResourceOverconsumption } from "@/app/api/report3";
-import React from "react";
-import ResourceOvercomsumptionReportGen from "./ResourceOverconsumptionReportGen";
-import ResourceOvercomsumptionReportGenLoading from "./ResourceOverconsumptionReportGenLoading";
-
-interface SubComponents {
- Loading: typeof ResourceOvercomsumptionReportGenLoading;
-}
-
-const ResourceOvercomsumptionReportGenWrapper: React.FC & SubComponents = async () => {
- const clentprojects = await fetchProjectsResourceOverconsumption();
-
- return ;
-};
-
-ResourceOvercomsumptionReportGenWrapper.Loading = ResourceOvercomsumptionReportGenLoading;
-
-export default ResourceOvercomsumptionReportGenWrapper;
\ No newline at end of file
diff --git a/src/components/Report/ResourceOverconsumptionReportGen/index.ts b/src/components/Report/ResourceOverconsumptionReportGen/index.ts
deleted file mode 100644
index 82fb633..0000000
--- a/src/components/Report/ResourceOverconsumptionReportGen/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-//src\components\DelayReportGen\index.ts
-export { default } from "./ResourceOverconsumptionReportGenWrapper";
diff --git a/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx
new file mode 100644
index 0000000..fb49dba
--- /dev/null
+++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx
@@ -0,0 +1,96 @@
+"use client";
+import React, { useMemo } from "react";
+import SearchBox, { Criterion } from "../SearchBox";
+import { useTranslation } from "react-i18next";
+import { ProjectResult } from "@/app/api/projects";
+import { fetchMonthlyWorkHoursReport, fetchProjectCashFlowReport, fetchProjectResourceOverconsumptionReport } from "@/app/api/reports/actions";
+import { downloadFile } from "@/app/utils/commonUtil";
+import { BASE_API_URL } from "@/config/api";
+import { ProjectResourceOverconsumptionReportFilter, ProjectResourceOverconsumptionReportRequest } from "@/app/api/reports";
+import { StaffResult } from "@/app/api/staff";
+import { TeamResult } from "@/app/api/team";
+import { Customer } from "@/app/api/customer";
+
+interface Props {
+ team: TeamResult[]
+ customer: Customer[]
+}
+
+type SearchQuery = Partial>;
+type SearchParamNames = keyof SearchQuery;
+
+const ResourceOverconsumptionReport: React.FC = ({ team, customer }) => {
+ const { t } = useTranslation("report");
+ const teamCombo = team.map(t => `${t.name} - ${t.code}`)
+ const custCombo = customer.map(c => `${c.name} - ${c.code}`)
+ const statusCombo = ["Overconsumption", "Potential Overconsumption"]
+ // const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`)
+ // console.log(staffs)
+
+ const searchCriteria: Criterion[] = useMemo(
+ () => [
+ {
+ label: t("Team"),
+ paramName: "team",
+ type: "select",
+ options: teamCombo,
+ needAll: true
+ },
+ {
+ label: t("Client"),
+ paramName: "customer",
+ type: "select",
+ options: custCombo,
+ needAll: true
+ },
+ {
+ label: t("Status"),
+ paramName: "status",
+ type: "select",
+ options: statusCombo,
+ needAll: true
+ },
+ {
+ label: t("lowerLimit"),
+ paramName: "lowerLimit",
+ type: "number",
+ },
+ ],
+ [t],
+ );
+
+return (
+ <>
+ {
+ let index = 0
+ let postData: ProjectResourceOverconsumptionReportRequest = {
+ status: "All",
+ lowerLimit: 0.9
+ }
+ if (query.team.length > 0 && query.team.toLocaleLowerCase() !== "all") {
+ index = teamCombo.findIndex(team => team === query.team)
+ postData.teamId = team[index].id
+ }
+ if (query.customer.length > 0 && query.customer.toLocaleLowerCase() !== "all") {
+ index = custCombo.findIndex(customer => customer === query.customer)
+ postData.custId = customer[index].id
+ }
+ if (Boolean(query.lowerLimit)) {
+ postData.lowerLimit = query.lowerLimit/100
+ }
+ postData.status = query.status
+ console.log(postData)
+ const response = await fetchProjectResourceOverconsumptionReport(postData)
+ if (response) {
+ downloadFile(new Uint8Array(response.blobValue), response.filename!!)
+ }
+ }
+ }
+ />
+>
+ )
+}
+
+export default ResourceOverconsumptionReport
\ No newline at end of file
diff --git a/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx
new file mode 100644
index 0000000..945d13c
--- /dev/null
+++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx
@@ -0,0 +1,41 @@
+//src\components\LateStartReportGen\LateStartReportGenLoading.tsx
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Skeleton from "@mui/material/Skeleton";
+import Stack from "@mui/material/Stack";
+import React from "react";
+
+// Can make this nicer
+export const ResourceOvercomsumptionReportLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ResourceOvercomsumptionReportLoading;
diff --git a/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx
new file mode 100644
index 0000000..1ab9d24
--- /dev/null
+++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+import ResourceOvercomsumptionReportLoading from "./ResourceOverconsumptionReportLoading";
+import ResourceOverconsumptionReport from "./ResourceOverconsumptionReport";
+import { fetchAllCustomers } from "@/app/api/customer";
+import { fetchTeam } from "@/app/api/team";
+
+interface SubComponents {
+ Loading: typeof ResourceOvercomsumptionReportLoading;
+}
+
+const ResourceOvercomsumptionReportWrapper: React.FC & SubComponents = async () => {
+ const customers = await fetchAllCustomers()
+ const teams = await fetchTeam ()
+
+ return ;
+};
+
+ResourceOvercomsumptionReportWrapper.Loading = ResourceOvercomsumptionReportLoading;
+
+export default ResourceOvercomsumptionReportWrapper;
\ No newline at end of file
diff --git a/src/components/ResourceOverconsumptionReport/index.ts b/src/components/ResourceOverconsumptionReport/index.ts
new file mode 100644
index 0000000..b5f20e2
--- /dev/null
+++ b/src/components/ResourceOverconsumptionReport/index.ts
@@ -0,0 +1 @@
+export { default } from "./ResourceOverconsumptionReportWrapper";
\ No newline at end of file
diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx
index cd0de38..3f849cd 100644
--- a/src/components/SearchBox/SearchBox.tsx
+++ b/src/components/SearchBox/SearchBox.tsx
@@ -4,7 +4,7 @@ import Grid from "@mui/material/Grid";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
-import React, { useCallback, useMemo, useState } from "react";
+import React, { FocusEvent, KeyboardEvent, PointerEvent, useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import TextField from "@mui/material/TextField";
import FormControl from "@mui/material/FormControl";
@@ -15,18 +15,29 @@ 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 FileDownload from "@mui/icons-material/FileDownload";
import dayjs from "dayjs";
import "dayjs/locale/zh-hk";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
-import { Box } from "@mui/material";
+import { Box, FormHelperText } from "@mui/material";
import { DateCalendar } from "@mui/x-date-pickers";
import { fetchLateStartReport } from "@/app/api/reports/actions";
import { LateStartReportRequest } from "@/app/api/reports";
import { fetchTeamCombo } from "@/app/api/team/actions";
import { downloadFile } from "@/app/utils/commonUtil";
+import {
+ Unstable_NumberInput as BaseNumberInput,
+ NumberInputProps,
+ numberInputClasses,
+} from "@mui/base/Unstable_NumberInput";
+import {
+ StyledButton,
+ StyledInputElement,
+ StyledInputRoot,
+} from "@/theme/colorConst";
+import { InputAdornment, NumberInput } from "../utils/numberInput";
interface BaseCriterion {
label: string;
@@ -47,23 +58,29 @@ interface SelectCriterion extends BaseCriterion {
interface DateRangeCriterion extends BaseCriterion {
type: "dateRange";
+ needMonth?: boolean;
}
interface MonthYearCriterion extends BaseCriterion {
type: "monthYear";
}
+interface NumberCriterion extends BaseCriterion {
+ type: "number";
+}
+
export type Criterion =
| TextCriterion
| SelectCriterion
| DateRangeCriterion
- | MonthYearCriterion;
+ | MonthYearCriterion
+ | NumberCriterion;
interface Props {
criteria: Criterion[];
onSearch: (inputs: Record) => void;
onReset?: () => void;
- formType?: String,
+ formType?: String;
}
function SearchBox({
@@ -79,10 +96,14 @@ function SearchBox({
(acc, c) => {
return {
...acc,
- [c.paramName]: c.type === "select" ?
- !(c.needAll === false) ? "All" :
- c.options.length > 0 ? c.options[0] : ""
- : ""
+ [c.paramName]:
+ c.type === "select"
+ ? !(c.needAll === false)
+ ? "All"
+ : c.options.length > 0
+ ? c.options[0]
+ : ""
+ : "",
};
},
{} as Record
@@ -90,7 +111,7 @@ function SearchBox({
[criteria]
);
const [inputs, setInputs] = useState(defaultInputs);
-
+
const makeInputChangeHandler = useCallback(
(paramName: T): React.ChangeEventHandler => {
return (e) => {
@@ -99,6 +120,15 @@ function SearchBox({
},
[]
);
+
+ const makeNumberChangeHandler = useCallback(
+ (paramName: T): (event: FocusEvent | PointerEvent | KeyboardEvent, value: number | null) => void => {
+ return (event, value) => {
+ setInputs((i) => ({ ...i, [paramName]: value }));
+ };
+ },
+ []
+ );
const makeSelectChangeHandler = useCallback((paramName: T) => {
return (e: SelectChangeEvent) => {
@@ -106,25 +136,37 @@ function SearchBox({
};
}, []);
- const makeDateChangeHandler = useCallback((paramName: T) => {
+ const makeDateChangeHandler = useCallback((paramName: T, needMonth?: boolean) => {
return (e: any) => {
- setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
+ if(needMonth){
+ setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM") }));
+ }else{
+ setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
+ }
+
};
}, []);
const makeMonthYearChangeHandler = useCallback((paramName: T) => {
return (e: any) => {
- console.log(dayjs(e).format("YYYY-MM"))
+ console.log(dayjs(e).format("YYYY-MM"));
setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM") }));
};
}, []);
- const makeDateToChangeHandler = useCallback((paramName: T) => {
+ const makeDateToChangeHandler = useCallback((paramName: T, needMonth?: boolean) => {
return (e: any) => {
+ if(needMonth){
+ setInputs((i) => ({
+ ...i,
+ [paramName + "To"]: dayjs(e).format("YYYY-MM"),
+ }));
+ }else{
setInputs((i) => ({
...i,
[paramName + "To"]: dayjs(e).format("YYYY-MM-DD"),
}));
+ }
};
}, []);
@@ -172,6 +214,15 @@ function SearchBox({
)}
+ {c.type === "number" && (
+ %}
+ />
+ )}
{c.type === "monthYear" && (
({
adapterLocale="zh-hk"
>
-
+
+
+
)}
@@ -202,12 +255,13 @@ function SearchBox({
({
@@ -246,7 +301,9 @@ function SearchBox({
) || }
+ startIcon={
+ (formType === "download" && ) ||
+ }
onClick={handleSearch}
>
{(formType === "download" && t("Download")) || t("Search")}
diff --git a/src/components/TimesheetModal/TimesheetModal.tsx b/src/components/TimesheetModal/TimesheetModal.tsx
index 8214462..66ccd76 100644
--- a/src/components/TimesheetModal/TimesheetModal.tsx
+++ b/src/components/TimesheetModal/TimesheetModal.tsx
@@ -26,6 +26,12 @@ import FullscreenModal from "../FullscreenModal";
import MobileTimesheetTable from "../TimesheetTable/MobileTimesheetTable";
import useIsMobile from "@/app/utils/useIsMobile";
import { HolidaysResult } from "@/app/api/holidays";
+import {
+ DAILY_NORMAL_MAX_HOURS,
+ TIMESHEET_DAILY_MAX_HOURS,
+ validateTimesheet,
+} from "@/app/api/timesheets/utils";
+import ErrorAlert from "../ErrorAlert";
interface Props {
isOpen: boolean;
@@ -77,6 +83,15 @@ const TimesheetModal: React.FC = ({
const onSubmit = useCallback>(
async (data) => {
+ const errors = validateTimesheet(data, leaveRecords, companyHolidays);
+ if (errors) {
+ Object.keys(errors).forEach((date) =>
+ formProps.setError(date, {
+ message: errors[date],
+ }),
+ );
+ return;
+ }
const savedRecords = await saveTimesheet(data, username);
const today = dayjs();
@@ -93,7 +108,7 @@ const TimesheetModal: React.FC = ({
formProps.reset(newFormValues);
onClose();
},
- [formProps, onClose, username],
+ [companyHolidays, formProps, leaveRecords, onClose, username],
);
const onCancel = useCallback(() => {
@@ -110,6 +125,20 @@ const TimesheetModal: React.FC = ({
[onClose],
);
+ const errorComponent = (
+ {
+ const error = formProps.formState.errors[date]?.message;
+ return error
+ ? `${date}: ${t(error, {
+ TIMESHEET_DAILY_MAX_HOURS,
+ DAILY_NORMAL_MAX_HOURS,
+ })}`
+ : undefined;
+ })}
+ />
+ );
+
const matches = useIsMobile();
return (
@@ -138,6 +167,7 @@ const TimesheetModal: React.FC = ({
leaveRecords={leaveRecords}
/>
+ {errorComponent}