diff --git a/public/temp/AR07_Project Claims Report.xlsx b/public/temp/AR07_Project Claims Report.xlsx new file mode 100644 index 0000000..95b2c13 Binary files /dev/null and b/public/temp/AR07_Project Claims Report.xlsx differ diff --git a/src/app/(main)/analytics/ProjectClaimsReport/page.tsx b/src/app/(main)/analytics/ProjectClaimsReport/page.tsx new file mode 100644 index 0000000..9f97f39 --- /dev/null +++ b/src/app/(main)/analytics/ProjectClaimsReport/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 ProjectClaimsReportComponent from "@/components/Report/ProjectClaimsReport"; + +export const metadata: Metadata = { + title: "Project Claims Report", +}; + +const ProjectClaimsReport: React.FC = () => { + return ( + + + Project Claims Report + + {/* }> + + */} + + + ); +}; +export default ProjectClaimsReport; diff --git a/src/app/api/report7/index.ts b/src/app/api/report7/index.ts new file mode 100644 index 0000000..c31a754 --- /dev/null +++ b/src/app/api/report7/index.ts @@ -0,0 +1,42 @@ +//src\app\api\report\index.ts +import { cache } from "react"; + +export interface ProjectClaims { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + staffName: string; +} + +export const preloadProjects = () => { + fetchProjectsProjectClaims(); +}; + +export const fetchProjectsProjectClaims = cache(async () => { + return mockProjects; +}); + +const mockProjects: ProjectClaims[] = [ + { + id: 1, + projectCode: "CUST-001", + projectName: "Client A", + team: "N/A", + teamLeader: "N/A", + startDate: "1/2/2024", + startDateFrom: "1/2/2024", + startDateTo: "1/2/2024", + targetEndDate: "30/3/2024", + client: "ss", + subsidiary: "sus", + staffName: "Leo", + }, +]; diff --git a/src/components/Report/ProjectClaimsReport/ProjectClaimsReport.tsx b/src/components/Report/ProjectClaimsReport/ProjectClaimsReport.tsx new file mode 100644 index 0000000..ca230e1 --- /dev/null +++ b/src/components/Report/ProjectClaimsReport/ProjectClaimsReport.tsx @@ -0,0 +1,17 @@ +//src\components\LateStartReport\LateStartReport.tsx +"use client"; +import * as React from "react"; +import "../../../app/global.css"; +import { Suspense } from "react"; +import ProjectClaimsReportGen from "@/components/Report/ProjectClaimsReportGen"; + +const ProjectClaimsReport: React.FC = () => { + + return ( + }> + + + ); +}; + +export default ProjectClaimsReport; \ No newline at end of file diff --git a/src/components/Report/ProjectClaimsReport/index.ts b/src/components/Report/ProjectClaimsReport/index.ts new file mode 100644 index 0000000..fc83eb4 --- /dev/null +++ b/src/components/Report/ProjectClaimsReport/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReport\index.ts +export { default } from "./ProjectClaimsReport"; diff --git a/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGen.tsx b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGen.tsx new file mode 100644 index 0000000..204b847 --- /dev/null +++ b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGen.tsx @@ -0,0 +1,44 @@ +//src\components\LateStartReportGen\LateStartReportGen.tsx +"use client"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../ReportSearchBox7"; +import { useTranslation } from "react-i18next"; +import { ProjectClaims } from "@/app/api/report7"; +//import { DownloadReportButton } from './DownloadReportButton'; +interface Props { + projects: ProjectClaims[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ProgressByClientSearch: React.FC = ({ projects }) => { + const { t } = useTranslation("projects"); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: "Report Period From", + label2: "Report Period To", + paramName: "targetEndDate", + type: "dateRange", + }, + { label: "Project Code", paramName: "projectCode", type: "select", options: ["M1963", "M1235", "M1476"] }, + { label: "Staff Name", paramName: "staffName", type: "select", options: ["Kennith", "Tom", "Cyril"] }, + ], + [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + {/* */} + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenLoading.tsx b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenLoading.tsx new file mode 100644 index 0000000..ab04aa4 --- /dev/null +++ b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenLoading.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 ProjectClaimsReportGenLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProjectClaimsReportGenLoading; diff --git a/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenWrapper.tsx b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenWrapper.tsx new file mode 100644 index 0000000..c5da0ec --- /dev/null +++ b/src/components/Report/ProjectClaimsReportGen/ProjectClaimsReportGenWrapper.tsx @@ -0,0 +1,19 @@ +//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx +import { fetchProjectsProjectClaims } from "@/app/api/report7"; +import React from "react"; +import ProjectClaimsReportGen from "./ProjectClaimsReportGen"; +import ProjectClaimsReportGenLoading from "./ProjectClaimsReportGenLoading"; + +interface SubComponents { + Loading: typeof ProjectClaimsReportGenLoading; +} + +const ProjectClaimsReportGenWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchProjectsProjectClaims(); + + return ; +}; + +ProjectClaimsReportGenWrapper.Loading = ProjectClaimsReportGenLoading; + +export default ProjectClaimsReportGenWrapper; \ No newline at end of file diff --git a/src/components/Report/ProjectClaimsReportGen/index.ts b/src/components/Report/ProjectClaimsReportGen/index.ts new file mode 100644 index 0000000..dab06a1 --- /dev/null +++ b/src/components/Report/ProjectClaimsReportGen/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReportGen\index.ts +export { default } from "./ProjectClaimsReportGenWrapper"; diff --git a/src/components/Report/ReportSearchBox7/SearchBox7.tsx b/src/components/Report/ReportSearchBox7/SearchBox7.tsx new file mode 100644 index 0000000..5ad1d83 --- /dev/null +++ b/src/components/Report/ReportSearchBox7/SearchBox7.tsx @@ -0,0 +1,465 @@ +//src\components\ReportSearchBox\SearchBox.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/AR07_Project Claims 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 }); + + // 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 G6 + // Apply styles from A5 to G5 (bold, bottom border, center alignment) + for (let col = 0; col < 7; col++) { // Columns A to G + 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 + ]; + const secondTableData = [ + ['Column1', 'Column2', 'Column3'], // Row 1 of second table + ['Data1', 'Data2', 'Data3'], // Row 2 of second table + // ... 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++; + } + + // Insert the first data form into the worksheet at the desired location + XLSX.utils.sheet_add_aoa(worksheet, firstTableData, { origin: { c: 0, r: lastRowOfFirstTable } }); + // Update lastRowOfFirstTable to account for the new data + lastRowOfFirstTable += firstTableData.length; + // Now insert the text that goes between the two tables + + // Insert the additional text with one row of spacing after the first table + const textRow = lastRowOfFirstTable + 1; // Adjust the 1 based on how many lines of spacing you want + XLSX.utils.sheet_add_aoa(worksheet, [['AR07 - Project Claims Report (Staff)']], { origin: { c: 0, r: textRow } }); + XLSX.utils.sheet_add_aoa(worksheet, [['Report Generation Date:']], { origin: { c: 0, r: (textRow+1) } }); + XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: { c: 2, r: (textRow+1) } }); + XLSX.utils.sheet_add_aoa(worksheet, [['Report Period:']], { origin: { c: 0, r: (textRow+2) } }); + //XLSX.utils.sheet_add_aoa(worksheet, [[]], { origin: { c: 2, r: (textRow+2) } }); + XLSX.utils.sheet_add_aoa(worksheet, [['Total Claim Amount (HKD):']], { origin: { c: 0, r: (textRow+3) } }); + XLSX.utils.sheet_add_aoa(worksheet, [[]], { origin: { c: 2, r: (textRow+3) } }); + // Row 6 is the template row we want to copy + const templateRow = 6; + // This is the new row where we want to copy the template row's content and style + const newRow = textRow + 6; + // Copy content and style from each cell in row 6 to newRow + for (let col = 0; col < 7; col++) { // Adjust the 7 if there are more columns + const sourceCellRef = XLSX.utils.encode_cell({ c: col, r: templateRow - 1 }); + const targetCellRef = XLSX.utils.encode_cell({ c: col, r: newRow - 1 }); + // If the source cell exists, copy its content and style + if (worksheet[sourceCellRef]) { + // Copy cell content + worksheet[targetCellRef] = { ...worksheet[sourceCellRef] }; + // If there is a style, we need to deep clone it to avoid references to the same style object + if (worksheet[sourceCellRef].s) { + worksheet[targetCellRef].s = JSON.parse(JSON.stringify(worksheet[sourceCellRef].s)); + } + } + } + + let secondTableStartRow = textRow + 6; + // Insert the second data form into the worksheet at the new starting row + XLSX.utils.sheet_add_aoa(worksheet, secondTableData, { origin: { c: 0, r: secondTableStartRow } }); + + // Source cell coordinates + const sourceCellCoord = { c: 2, r: 2 }; // C3 (columns and rows are 0-indexed in this library) + // Target cell coordinates + const targetCellCoord = { c: 2, r: textRow + 2 }; + // Create references for source and target cells + const sourceCellRef = XLSX.utils.encode_cell(sourceCellCoord); + const targetCellRef = XLSX.utils.encode_cell(targetCellCoord); + // Copy the cell content from C3 to the target cell + if (worksheet[sourceCellRef]) { + worksheet[targetCellRef] = { ...worksheet[sourceCellRef] }; + // If the source cell has a style, deep clone it for the target cell + if (worksheet[sourceCellRef].s) { + worksheet[targetCellRef].s = JSON.parse(JSON.stringify(worksheet[sourceCellRef].s)); + } + } + // Define the range of cells to merge + const mergeRangeA1 = { + s: { c: 0, r: textRow}, // Start cell + e: { c: 3, r: textRow} // End cell + }; + // Add the range to the 'merges' array in the worksheet if it doesn't exist + if (!worksheet['!merges']) worksheet['!merges'] = []; + worksheet['!merges'].push(mergeRangeA1); + // Apply center alignment to the merged cell + const mergedCellRefA1 = XLSX.utils.encode_cell({ c: 0, r: textRow}); + if (!worksheet[mergedCellRefA1]) { + worksheet[mergedCellRefA1] = {}; // Create the cell if it doesn't exist + } + worksheet[mergedCellRefA1].s = { + alignment: {horizontal: "left",vertical: "center",wrapText: true} + }; + + // Define the range of cells to merge { c: 0, r: (textRow+1) } to { c: 1, r: (textRow+1) } + const mergeRangeA2 = { + s: { c: 0, r: textRow + 1 }, // Start cell + e: { c: 1, r: textRow + 1 } // End cell + }; + // Add the range to the 'merges' array in the worksheet if it doesn't exist + if (!worksheet['!merges']) worksheet['!merges'] = []; + worksheet['!merges'].push(mergeRangeA2); + // Apply center alignment to the merged cell + const mergedCellRefA2 = XLSX.utils.encode_cell({ c: 0, r: textRow + 1 }); + if (!worksheet[mergedCellRefA2]) { + worksheet[mergedCellRefA2] = {}; // Create the cell if it doesn't exist + } + worksheet[mergedCellRefA2].s = { + alignment: {horizontal: "left",vertical: "center",wrapText: true} + }; + + // Define the range of cells to merge + const mergeRangeA3 = { + s: { c: 0, r: textRow + 2 }, // Start cell + e: { c: 1, r: textRow + 2 } // End cell + }; + // Add the range to the 'merges' array in the worksheet if it doesn't exist + if (!worksheet['!merges']) worksheet['!merges'] = []; + worksheet['!merges'].push(mergeRangeA3); + // Apply center alignment to the merged cell + const mergedCellRefA3 = XLSX.utils.encode_cell({ c: 0, r: textRow + 2 }); + if (!worksheet[mergedCellRefA3]) { + worksheet[mergedCellRefA3] = {}; // Create the cell if it doesn't exist + } + worksheet[mergedCellRefA3].s = { + alignment: {horizontal: "left",vertical: "center",wrapText: true} + }; + + // Define the range of cells to merge + const mergeRangeA4 = { + s: { c: 0, r: textRow + 3 }, // Start cell + e: { c: 1, r: textRow + 3 } // End cell + }; + // Add the range to the 'merges' array in the worksheet if it doesn't exist + if (!worksheet['!merges']) worksheet['!merges'] = []; + worksheet['!merges'].push(mergeRangeA4); + // Apply center alignment to the merged cell + const mergedCellRefA4 = XLSX.utils.encode_cell({ c: 0, r: textRow + 3 }); + if (!worksheet[mergedCellRefA4]) { + worksheet[mergedCellRefA4] = {}; // Create the cell if it doesn't exist + } + worksheet[mergedCellRefA4].s = { + alignment: {horizontal: "left",vertical: "center",wrapText: true} + }; + + // Style for the cell at { c: 0, r: textRow } + const tStyle = { + font: { bold: true, sz: 16, color: { rgb: "000000" } }, // Example: Black, bold, 12pt font + alignment: { horizontal: "left", vertical: "center" }, + // Add any additional styling properties here + }; + + // Apply the unique style to the cell at { c: 0, r: textRow } + const cellRefUnique = XLSX.utils.encode_cell({ c: 0, r: textRow }); + worksheet[cellRefUnique].s = tStyle; + + // Style for the other cells + const stStyle = { + font: { bold: true, sz: 11, color: { rgb: "000000" } }, + alignment: { horizontal: "left", vertical: "center" }, + // Add any additional styling properties here + }; + + // Apply the same style to the cells at { c: 0, r: textRow+1 }, { c: 0, r: textRow+2 }, { c: 0, r: textRow+3 } + for (let i = 1; i <= 3; i++) { + const cellRefOther = XLSX.utils.encode_cell({ c: 0, r: textRow + i }); + if (!worksheet[cellRefOther]) { + worksheet[cellRefOther] = {}; // Create the cell if it doesn't exist + } + worksheet[cellRefOther].s = stStyle; + } + + // 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 = `AR07_Project_Claims_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" && ( + + + + + + + {"-"} + + + + + + + )} + + ); + })} + + + + + + + + ); +} + +export default SearchBox; diff --git a/src/components/Report/ReportSearchBox7/index.ts b/src/components/Report/ReportSearchBox7/index.ts new file mode 100644 index 0000000..b3a9815 --- /dev/null +++ b/src/components/Report/ReportSearchBox7/index.ts @@ -0,0 +1,3 @@ +//src\components\SearchBox\index.ts +export { default } from "./SearchBox7"; +export type { Criterion } from "./SearchBox7";