diff --git a/package.json b/package.json index 5db5867..2578a2f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@emotion/cache": "^11.11.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.4.1", "@fontsource/inter": "^5.0.16", "@fontsource/plus-jakarta-sans": "^5.0.18", "@mui/icons-material": "^5.15.0", @@ -38,7 +39,8 @@ "react-select": "^5.8.0", "reactstrap": "^9.2.2", "styled-components": "^6.1.8", - "sweetalert2": "^11.10.3" + "sweetalert2": "^11.10.3", + "xlsx-js-style": "^1.2.0" }, "devDependencies": { "@types/lodash": "^4.14.202", diff --git a/public/temp/AR01_Late Start Report.xlsx b/public/temp/AR01_Late Start Report.xlsx new file mode 100644 index 0000000..03d6b6b Binary files /dev/null and b/public/temp/AR01_Late Start Report.xlsx differ diff --git a/src/app/(main)/analytics/CostandExpenseReport/page.tsx b/src/app/(main)/analytics/CostandExpenseReport/page.tsx new file mode 100644 index 0000000..2f046c3 --- /dev/null +++ b/src/app/(main)/analytics/CostandExpenseReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Cost and Expense Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/DelayReport/page.tsx b/src/app/(main)/analytics/DelayReport/page.tsx new file mode 100644 index 0000000..154fd58 --- /dev/null +++ b/src/app/(main)/analytics/DelayReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Delay Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/LateStartReport/page.tsx b/src/app/(main)/analytics/LateStartReport/page.tsx new file mode 100644 index 0000000..7f4ade5 --- /dev/null +++ b/src/app/(main)/analytics/LateStartReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Late Start Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ProjectCompletionReport/page.tsx b/src/app/(main)/analytics/ProjectCompletionReport/page.tsx new file mode 100644 index 0000000..c938efc --- /dev/null +++ b/src/app/(main)/analytics/ProjectCompletionReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Project Completion Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx b/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx new file mode 100644 index 0000000..5b914ad --- /dev/null +++ b/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Project Completion Report with Outstanding Un-billed Hours + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx b/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx new file mode 100644 index 0000000..4f5c75a --- /dev/null +++ b/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Resource Overconsumption Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/page.tsx b/src/app/(main)/analytics/page.tsx index 9ca9a24..d20fa34 100644 --- a/src/app/(main)/analytics/page.tsx +++ b/src/app/(main)/analytics/page.tsx @@ -1,3 +1,4 @@ +//src\app\(main)\analytics\page.tsx import { Metadata } from "next"; export const metadata: Metadata = { @@ -5,7 +6,8 @@ export const metadata: Metadata = { }; const Analytics: React.FC = async () => { - return "Analytics"; + //return "Analytics"; + return
Analytics
; }; export default Analytics; diff --git a/src/app/api/report/index.ts b/src/app/api/report/index.ts new file mode 100644 index 0000000..588692c --- /dev/null +++ b/src/app/api/report/index.ts @@ -0,0 +1,44 @@ +//src\app\api\report\index.ts +import { cache } from "react"; + +export interface LateStart { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + nextstage: string; + nextstageenddate: string; +} + +export const preloadProjects = () => { + fetchProjectsCashFlow(); +}; + +export const fetchProjectsCashFlow = cache(async () => { + return mockProjects; +}); + +const mockProjects: LateStart[] = [ + { + 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", + nextstage:"s1", + nextstageenddate:"30/2/2024", + }, +]; diff --git a/src/components/LateStartReport/LateStartReport.tsx b/src/components/LateStartReport/LateStartReport.tsx new file mode 100644 index 0000000..85df0b9 --- /dev/null +++ b/src/components/LateStartReport/LateStartReport.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 LateStartReportGen from "@/components/LateStartReportGen"; + +const LateStartReport: React.FC = () => { + + return ( + }> + + + ); +}; + +export default LateStartReport; \ No newline at end of file diff --git a/src/components/LateStartReport/index.ts b/src/components/LateStartReport/index.ts new file mode 100644 index 0000000..bb8ef69 --- /dev/null +++ b/src/components/LateStartReport/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReport\index.ts +export { default } from "./LateStartReport"; diff --git a/src/components/LateStartReportGen/DownloadReportButton.tsx b/src/components/LateStartReportGen/DownloadReportButton.tsx new file mode 100644 index 0000000..b838fba --- /dev/null +++ b/src/components/LateStartReportGen/DownloadReportButton.tsx @@ -0,0 +1,40 @@ +// DownloadReportButton.tsx +// import React, { useState } from 'react'; +// import { generateFakeData } from '../utils/generateFakeData'; +// import { downloadExcel } from '../utils/downloadExcel'; + +// export const DownloadReportButton: React.FC = () => { +// const [isLoading, setIsLoading] = useState(false); + +// const handleDownload = async () => { +// setIsLoading(true); +// const data = generateFakeData(10); +// downloadExcel(data); +// setIsLoading(false); +// }; + +// return ( +// +// ); +// }; + +import React from 'react'; + +export const DownloadReportButton: React.FC = () => { + const handleDownload = () => { + const link = document.createElement('a'); + link.href = '/temp/AR01_Late Start Report.xlsx'; // Adjust the path as necessary + link.download = 'AR01_Late Start Report.xlsx'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/LateStartReportGen/LateStartReportGen.tsx b/src/components/LateStartReportGen/LateStartReportGen.tsx new file mode 100644 index 0000000..8ebfae2 --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGen.tsx @@ -0,0 +1,44 @@ +//src\components\LateStartReportGen\LateStartReportGen.tsx +"use client"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import { CashFlow } from "@/app/api/cashflow"; +import { DownloadReportButton } from './DownloadReportButton'; +interface Props { + projects: CashFlow[]; +} +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: "text" }, + { label: "Client", paramName: "client", type: "text" }, + { + label: "Remained Date From", + label2: "Remained Date To", + paramName: "targetEndDate", + type: "dateRange", + }, + ], + [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/LateStartReportGen/LateStartReportGenLoading.tsx b/src/components/LateStartReportGen/LateStartReportGenLoading.tsx new file mode 100644 index 0000000..93143ce --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGenLoading.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 LateStartReportGenLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default LateStartReportGenLoading; diff --git a/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx b/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx new file mode 100644 index 0000000..5ba8325 --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx @@ -0,0 +1,19 @@ +//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx +import { fetchProjectsCashFlow } from "@/app/api/cashflow"; +import React from "react"; +import LateStartReportGen from "./LateStartReportGen"; +import LateStartReportGenLoading from "./LateStartReportGenLoading"; + +interface SubComponents { + Loading: typeof LateStartReportGenLoading; +} + +const LateStartReportGenWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchProjectsCashFlow(); + + return ; +}; + +LateStartReportGenWrapper.Loading = LateStartReportGenLoading; + +export default LateStartReportGenWrapper; \ No newline at end of file diff --git a/src/components/LateStartReportGen/index.ts b/src/components/LateStartReportGen/index.ts new file mode 100644 index 0000000..55e0f5d --- /dev/null +++ b/src/components/LateStartReportGen/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReportGen\index.ts +export { default } from "./LateStartReportGenWrapper"; diff --git a/src/components/ReportSearchBox/SearchBox.tsx b/src/components/ReportSearchBox/SearchBox.tsx new file mode 100644 index 0000000..fe058f7 --- /dev/null +++ b/src/components/ReportSearchBox/SearchBox.tsx @@ -0,0 +1,201 @@ +"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"; + +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); + }; + + 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/ReportSearchBox/index.ts b/src/components/ReportSearchBox/index.ts new file mode 100644 index 0000000..37a0659 --- /dev/null +++ b/src/components/ReportSearchBox/index.ts @@ -0,0 +1,3 @@ +//src\components\SearchBox\index.ts +export { default } from "./SearchBox"; +export type { Criterion } from "./SearchBox"; diff --git a/src/components/utils/downloadExcel.ts b/src/components/utils/downloadExcel.ts new file mode 100644 index 0000000..825ccb3 --- /dev/null +++ b/src/components/utils/downloadExcel.ts @@ -0,0 +1,9 @@ +// downloadExcel.ts +import * as XLSX from 'xlsx-js-style'; + +export const downloadExcel = (data: any[]) => { + const worksheet = XLSX.utils.json_to_sheet(data); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Report'); + XLSX.writeFile(workbook, 'Report.xlsx'); +}; diff --git a/src/components/utils/generateFakeData.ts b/src/components/utils/generateFakeData.ts new file mode 100644 index 0000000..2b2df45 --- /dev/null +++ b/src/components/utils/generateFakeData.ts @@ -0,0 +1,40 @@ +// generateFakeData.ts +import { faker } from '@faker-js/faker'; + +interface ProjectData { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + nextstage: string; + nextstageenddate: string; +} + +export const generateFakeData = (numEntries: number): ProjectData[] => { + const data: ProjectData[] = []; + for (let i = 0; i < numEntries; i++) { + data.push({ + id: i + 1, + projectCode: faker.datatype.uuid(), + projectName: faker.commerce.productName(), + team: faker.commerce.department(), + teamLeader: faker.name.fullName(), // Corrected from findName to fullName + startDate: faker.date.recent(90).toISOString().split('T')[0], + startDateFrom: faker.date.past(1).toISOString().split('T')[0], + startDateTo: faker.date.future(1).toISOString().split('T')[0], + targetEndDate: faker.date.future(1).toISOString().split('T')[0], + client: faker.company.name(), // Corrected from companyName to name + subsidiary: faker.company.name(), // Corrected from companyName to name + nextstage: "Design", + nextstageenddate: faker.date.future(2).toISOString().split('T')[0], + }); + } + return data; +};