diff --git a/public/temp/EX01_Financial Status Report.xlsx b/public/temp/EX01_Financial Status Report.xlsx
new file mode 100644
index 0000000..bd1b55d
Binary files /dev/null and b/public/temp/EX01_Financial Status Report.xlsx differ
diff --git a/src/app/(main)/analytics/FinancialStatusReport/page.tsx b/src/app/(main)/analytics/FinancialStatusReport/page.tsx
new file mode 100644
index 0000000..0a3865d
--- /dev/null
+++ b/src/app/(main)/analytics/FinancialStatusReport/page.tsx
@@ -0,0 +1,24 @@
+//src\app\(main)\analytics\DelayReport\page.tsx
+import { Metadata } from "next";
+import { I18nProvider } from "@/i18n";
+import Typography from "@mui/material/Typography";
+import FinancialStatusReportComponent from "@/components/Report/FinancialStatusReport";
+
+export const metadata: Metadata = {
+ title: "Financial Status Report",
+};
+
+const ProjectFinancialStatusReport: React.FC = () => {
+ return (
+
+
+ Financial Status Report
+
+ {/* }>
+
+ */}
+
+
+ );
+};
+export default ProjectFinancialStatusReport;
diff --git a/src/app/api/reporte1/index.ts b/src/app/api/reporte1/index.ts
new file mode 100644
index 0000000..5e27648
--- /dev/null
+++ b/src/app/api/reporte1/index.ts
@@ -0,0 +1,42 @@
+//src\app\api\report\index.ts
+import { cache } from "react";
+
+export interface FinancialStatus {
+ id: number;
+ projectCode: string;
+ projectName: string;
+ team: string;
+ teamLeader: string;
+ startDate: string;
+ startDateFrom: string;
+ startDateTo: string;
+ targetEndDate: string;
+ client: string;
+ subsidiary: string;
+ status: string;
+}
+
+export const preloadProjects = () => {
+ fetchProjectsFinancialStatus();
+};
+
+export const fetchProjectsFinancialStatus = cache(async () => {
+ return mockProjects;
+});
+
+const mockProjects: FinancialStatus[] = [
+ {
+ id: 1,
+ projectCode: "CUST-001",
+ projectName: "Client A",
+ team: "N/A",
+ teamLeader: "N/A",
+ startDate: "5",
+ startDateFrom: "5",
+ startDateTo: "5",
+ targetEndDate: "s",
+ client: "ss",
+ subsidiary: "ss",
+ status: "1",
+ },
+];
diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx
index ad68823..45bfda9 100644
--- a/src/components/NavigationContent/NavigationContent.tsx
+++ b/src/components/NavigationContent/NavigationContent.tsx
@@ -108,6 +108,7 @@ const navigationItems: NavigationItem[] = [
{icon: , label:"Completion Report with Outstanding Un-billed Hours Report", path: "/analytics/ProjectCompletionReportWO"},
{icon: , label:"Project Claims Report", path: "/analytics/ProjectClaimsReport"},
{icon: , label:"Project P&L Report", path: "/analytics/ProjectPLReport"},
+ {icon: , label:"Financial Status Report", path: "/analytics/FinancialStatusReport"},
],
},
{
diff --git a/src/components/Report/FinancialStatusReport/FinancialStatusReport.tsx b/src/components/Report/FinancialStatusReport/FinancialStatusReport.tsx
new file mode 100644
index 0000000..893b0ba
--- /dev/null
+++ b/src/components/Report/FinancialStatusReport/FinancialStatusReport.tsx
@@ -0,0 +1,17 @@
+//src\components\DelayReport\DelayReport.tsx
+"use client";
+import * as React from "react";
+import "../../../app/global.css";
+import { Suspense } from "react";
+import FinancialStatusReportGen from "@/components/Report/FinancialStatusReportGen";
+
+const FinancialStatusReport: React.FC = () => {
+
+ return (
+ }>
+
+
+ );
+};
+
+export default FinancialStatusReport;
\ No newline at end of file
diff --git a/src/components/Report/FinancialStatusReport/index.ts b/src/components/Report/FinancialStatusReport/index.ts
new file mode 100644
index 0000000..4500704
--- /dev/null
+++ b/src/components/Report/FinancialStatusReport/index.ts
@@ -0,0 +1,2 @@
+//src\components\LateStartReport\index.ts
+export { default } from "./FinancialStatusReport";
diff --git a/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGen.tsx b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGen.tsx
new file mode 100644
index 0000000..bf4a7cd
--- /dev/null
+++ b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGen.tsx
@@ -0,0 +1,43 @@
+//src\components\LateStartReportGen\LateStartReportGen.tsx
+"use client";
+import React, { useMemo, useState } from "react";
+import SearchBox, { Criterion } from "../ReportSearchBoxe1";
+import { useTranslation } from "react-i18next";
+import { FinancialStatus } from "@/app/api/reporte1";
+//import { DownloadReportButton } from './DownloadReportButton';
+interface Props {
+ projects: FinancialStatus[];
+}
+type SearchQuery = Partial>;
+type SearchParamNames = keyof SearchQuery;
+
+const ProgressByClientSearch: React.FC = ({ projects }) => {
+ const { t } = useTranslation("projects");
+
+ const searchCriteria: Criterion[] = useMemo(
+ () => [
+ { label: "{Project Code}", paramName: "projectCode", type: "select", options: ["M1234", "M1268", "M1352", "M1393"] },
+ // {
+ // label: "Status",
+ // label2: "Remained Date To",
+ // paramName: "targetEndDate",
+ // type: "dateRange",
+ // },
+ ],
+ [t],
+ );
+
+ return (
+ <>
+ {
+ console.log(query);
+ }}
+ />
+ {/* */}
+ >
+ );
+};
+
+export default ProgressByClientSearch;
diff --git a/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenLoading.tsx b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenLoading.tsx
new file mode 100644
index 0000000..6d992f9
--- /dev/null
+++ b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenLoading.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 FinancialStatusReportGenLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default FinancialStatusReportGenLoading;
diff --git a/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenWrapper.tsx b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenWrapper.tsx
new file mode 100644
index 0000000..e82ea4b
--- /dev/null
+++ b/src/components/Report/FinancialStatusReportGen/FinancialStatusReportGenWrapper.tsx
@@ -0,0 +1,19 @@
+//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx
+import { fetchProjectsFinancialStatus } from "@/app/api/reporte1";
+import React from "react";
+import FinancialStatusReportGen from "./FinancialStatusReportGen";
+import FinancialStatusReportGenLoading from "./FinancialStatusReportGenLoading";
+
+interface SubComponents {
+ Loading: typeof FinancialStatusReportGenLoading;
+}
+
+const FinancialStatusReportGenWrapper: React.FC & SubComponents = async () => {
+ const clentprojects = await fetchProjectsFinancialStatus();
+
+ return ;
+};
+
+FinancialStatusReportGenWrapper.Loading = FinancialStatusReportGenLoading;
+
+export default FinancialStatusReportGenWrapper;
\ No newline at end of file
diff --git a/src/components/Report/FinancialStatusReportGen/index.ts b/src/components/Report/FinancialStatusReportGen/index.ts
new file mode 100644
index 0000000..d53d85b
--- /dev/null
+++ b/src/components/Report/FinancialStatusReportGen/index.ts
@@ -0,0 +1,2 @@
+//src\components\DelayReportGen\index.ts
+export { default } from "./FinancialStatusReportGenWrapper";
diff --git a/src/components/Report/ReportSearchBoxe1/SearchBoxe1.tsx b/src/components/Report/ReportSearchBoxe1/SearchBoxe1.tsx
new file mode 100644
index 0000000..ea7afb6
--- /dev/null
+++ b/src/components/Report/ReportSearchBoxe1/SearchBoxe1.tsx
@@ -0,0 +1,482 @@
+//src\components\ReportSearchBox\SearchBox2.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"),
+ }));
+ };
+ }, []);
+
+ interface CellValue {
+ v: number | string; // Value of the cell
+ t: 'n' | 's'; // Type of the cell value: 'n' for number, 's' for string
+ s?: XLSX.CellStyle; // Optional style for the cell
+ }
+
+ const handleReset = () => {
+ setInputs(defaultInputs);
+ onReset?.();
+ };
+
+ const handleSearch = () => {
+ onSearch(inputs);
+
+ };
+
+ // Function to merge cells from A2:B2 to A14:B14
+function mergeCells(worksheet: XLSX.WorkSheet) {
+ // Ensure the 'merges' array exists in the worksheet
+ if (!worksheet['!merges']) worksheet['!merges'] = [];
+
+ // Loop through rows 2 to 14 (0-indexed + 1)
+ for (let row = 1; row <= 13; row++) {
+ // Define the range for current row to merge A and B columns
+ const mergeRange = {
+ s: { c: 0, r: row }, // Start cell (Column A)
+ e: { c: 1, r: row } // End cell (Column B)
+ };
+ // Add the range to the 'merges' array in the worksheet
+ worksheet['!merges'].push(mergeRange);
+ // Apply center alignment to the merged cell
+ const mergedCellRef = XLSX.utils.encode_cell({ c: 0, r: row });
+ if (!worksheet[mergedCellRef]) {
+ worksheet[mergedCellRef] = {}; // Create the cell if it doesn't exist
+ }
+ worksheet[mergedCellRef].s = {
+ alignment: { horizontal: "left", wrapText: true }
+ };
+ }
+}
+
+// Processing and inserting table data with calculations
+function processDataAndInsert(worksheet: XLSX.WorkSheet, startRow:number, data:(string|number)[][]) {
+ data.forEach((row, rowIndex) => {
+ const r = startRow + rowIndex;
+
+ // Direct assignments for columns A-F as strings
+ const stringCols = ['A', 'B', 'C', 'D', 'E', 'F'];
+ stringCols.forEach((col, index) => {
+ const cellRef = col + r;
+ worksheet[cellRef] = { v: row[index], t: 's' }; // Force type as string
+ });
+
+ // Assignments for columns G-O as numbers
+ const numberCols = ['G', 'H', 'I', 'K', 'N'];
+ const colIndices = [6, 7, 8, 9, 10]; // Indices in the data array corresponding to G, H, I, K, N
+ numberCols.forEach((col, index) => {
+ const cellRef = col + r;
+ worksheet[cellRef] = { v: row[colIndices[index]], t: 'n' }; // Force type as number
+ });
+
+ // Calculations for columns J, L, M, O
+ const h = row[6] as number;
+ const i = row[7] as number;
+ const k = row[9] as number;
+ const n = row[10] as number;
+
+ // Column J: H - I
+ worksheet['J' + r] = { v: h - i, t: 'n' };
+
+ // Column L: IF(H {
+ const cellRefs = data.map((_, index) => col + (startRow + index));
+ const formula = `=SUM(${cellRefs.join(',')})`;
+ worksheet[col + sumRow] = { f: formula, t: 'n', s: {
+ border: {
+ top: {style: 'thin', color: {auto: 1}},
+ bottom: {style: 'double', color: {auto: 1}}
+ }
+ }};
+ });
+ XLSX.utils.sheet_add_aoa(worksheet, [['Sub-total']], { origin: { c: 0, r: (sumRow-1) } });
+
+// const mergedCellRefA1 = XLSX.utils.encode_cell({ c: 0, r: sumRow-1});
+// if (!worksheet[mergedCellRefA1]) {
+// worksheet[mergedCellRefA1] = {}; // Create the cell if it doesn't exist
+// }
+// // Apply right alignment, center vertical alignment, wrap text, and border styles to the 'Sub-total' cell
+// worksheet[mergedCellRefA1].s = {
+// alignment: {horizontal: "right", vertical: "center", wrapText: true},
+// border: {
+// top: {style: 'thin', color: {auto: 1}},
+// bottom: {style: 'double', color: {auto: 1}}
+// }
+// };
+// Define the range of cells to merge for 'Sub-total'
+const mergeRangeSubTotal = {
+ s: { c: 0, r: sumRow-1}, // Start at column A
+ e: { c: 5, r: sumRow-1} // End at column F
+};
+// // Add the range to the 'merges' array in the worksheet if it doesn't exist
+// if (!worksheet['!merges']) worksheet['!merges'] = [];
+// worksheet['!merges'].push(mergeRangeSubTotal);
+
+// Update styles for the merged cell range where 'Sub-total' is located
+const mergedCellRefSubTotal = XLSX.utils.encode_cell({ c: 0, r: sumRow-1 });
+if (!worksheet[mergedCellRefSubTotal]) {
+ worksheet[mergedCellRefSubTotal] = {}; // Create the cell if it doesn't exist
+}
+worksheet[mergedCellRefSubTotal].s = {
+ alignment: {horizontal: "right", vertical: "center", wrapText: true},
+ border: {
+ top: {style: 'thin', color: {auto: 1}},
+ bottom: {style: 'double', color: {auto: 1}}}
+};
+// Add the range to the 'merges' array in the worksheet if it doesn't exist
+if (!worksheet['!merges']) worksheet['!merges'] = [];
+worksheet['!merges'].push(mergeRangeSubTotal)
+
+
+const mergedCellRefM1 = XLSX.utils.encode_cell({ c: 12, r: sumRow});
+if (!worksheet[mergedCellRefM1]) {
+ worksheet[mergedCellRefM1] = {}; // Create the cell if it doesn't exist
+}
+worksheet[mergedCellRefM1].s = {
+ alignment: {horizontal: "right", vertical: "center", wrapText: true},
+ border: {
+ top: {style: 'thin', color: {auto: 1}},
+ bottom: {style: 'double', color: {auto: 1}}
+ }
+};
+
+}
+const firstTableData = [
+ ['Code1', 'PJName1', 'Client1','Team1','2011/01/01','2011/02/01','625','500','350','350','171'], // Row 1
+ ['Code2', 'PJName2', 'Client2','Team2','2011/03/01','2011/04/01','1000','800','565','565','565'],// Row 2
+ ['Code2', 'PJName2', 'Client2','Team2','2011/03/01','2011/04/01','1000','800','565','565','565'],// Row 3
+ // ... more rows as needed
+];
+
+ const handleDownload = async () => {
+ //setIsLoading(true);
+
+ try {
+ const response = await fetch('/temp/EX01_Financial Status Report.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 });
+
+ mergeCells(worksheet);
+
+ // Style for cell A1: Font size 16 and bold
+ if (worksheet['A1']) {
+ worksheet['A1'].s = {
+ font: {bold: true,sz: 16,},alignment: { horizontal: 'center' } // Font size 16 //name: 'Times New Roman' // Specify font
+ };
+ }
+
+ // Apply styles from A2 A3 A5 (bold)
+ ['A2', 'A3', 'A5','A14'].forEach(cell => {
+ if (worksheet[cell]) {
+ worksheet[cell].s = { font: { bold: true },alignment: { horizontal: 'left' } };
+ }
+ });
+
+ // Apply styles from A2 A3 A5 (bold)
+ ['A6', 'A7', 'A8','A9','A10','A11','A12'].forEach(cell => {
+ if (worksheet[cell]) {
+ worksheet[cell].s = { font: { bold: false },alignment: { horizontal: 'left' } };
+ }
+ });
+
+ // Formatting from A15 to O15
+ // Apply styles from A6 to K6 (bold, bottom border, center alignment)
+ for (let col = 0; col < 15; col++) { // Columns A to O
+ const cellRef = XLSX.utils.encode_col(col) + '15';
+ if (worksheet[cellRef]) {
+ worksheet[cellRef].s = {
+ font: { bold: true },
+ alignment: { horizontal: 'center' },
+ border: {
+ bottom: { style: 'thin', color: { auto: 1 } }
+ }
+ };
+ }
+ }
+
+ // Find the last row of the first table
+ let lastRowOfFirstTable = 16; // Starting row for data in the first table
+ while (worksheet[XLSX.utils.encode_cell({ c: 0, r: lastRowOfFirstTable })]) {
+ lastRowOfFirstTable++;
+ }
+ // Insert the first data form into the worksheet at the desired location
+ //XLSX.utils.sheet_add_aoa(worksheet, firstTableData, { origin: { c: 0, r: lastRowOfFirstTable } });
+ // Assuming worksheet is already defined, and we start inserting from row 16
+processDataAndInsert(worksheet, 16, firstTableData);
+ // Update lastRowOfFirstTable to account for the new data
+ lastRowOfFirstTable += firstTableData.length;
+ // Now insert the text that goes between the two tables
+
+ // Calculate the maximum length of content in each column and set column width
+ const colWidths: number[] = [];
+
+ // Start with a base width for each column (optional, but can help with columns that have no data)
+ // Check if worksheet['!ref'] is defined to prevent errors
+ const maxCol = worksheet['!ref'] ? worksheet['!ref'].split(':')[1].charCodeAt(0) - 'A'.charCodeAt(0) + 1 : 0;
+ for (let col = 0; col < maxCol; col++) {
+ colWidths[col] = 10; // Default base width
+ }
+
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][];
+
+ // Skip the first row in the jsonData
+ for (let row = 1; row < jsonData.length; row++) {
+ jsonData[row].forEach((cell, index) => {
+ // Only process if the cell is not null/undefined
+ if (cell) {
+ const valueLength = cell.toString().length;
+ colWidths[index] = Math.max(colWidths[index] || 0, valueLength);
+ }
+ });
+ }
+
+ // Check if worksheet exists before setting '!cols'
+ if (worksheet) {
+ worksheet['!cols'] = colWidths.map((width) => ({ wch: width + 2 })); // +2 for a little extra padding
+ }
+
+ // 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 = `EX01_Financial_Status_Report_${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/ReportSearchBoxe1/index.ts b/src/components/Report/ReportSearchBoxe1/index.ts
new file mode 100644
index 0000000..8f4d421
--- /dev/null
+++ b/src/components/Report/ReportSearchBoxe1/index.ts
@@ -0,0 +1,3 @@
+//src\components\SearchBox\index.ts
+export { default } from "./SearchBoxe1";
+export type { Criterion } from "./SearchBoxe1";