Просмотр исходного кода

add report page

tags/Baseline_30082024_FRONTEND_UAT
leoho2fi 1 год назад
Родитель
Сommit
b612d7fbd1
21 измененных файлов: 612 добавлений и 2 удалений
  1. +3
    -1
      package.json
  2. Двоичные данные
      public/temp/AR01_Late Start Report.xlsx
  3. +24
    -0
      src/app/(main)/analytics/CostandExpenseReport/page.tsx
  4. +24
    -0
      src/app/(main)/analytics/DelayReport/page.tsx
  5. +24
    -0
      src/app/(main)/analytics/LateStartReport/page.tsx
  6. +24
    -0
      src/app/(main)/analytics/ProjectCompletionReport/page.tsx
  7. +24
    -0
      src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx
  8. +24
    -0
      src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx
  9. +3
    -1
      src/app/(main)/analytics/page.tsx
  10. +44
    -0
      src/app/api/report/index.ts
  11. +17
    -0
      src/components/LateStartReport/LateStartReport.tsx
  12. +2
    -0
      src/components/LateStartReport/index.ts
  13. +40
    -0
      src/components/LateStartReportGen/DownloadReportButton.tsx
  14. +44
    -0
      src/components/LateStartReportGen/LateStartReportGen.tsx
  15. +41
    -0
      src/components/LateStartReportGen/LateStartReportGenLoading.tsx
  16. +19
    -0
      src/components/LateStartReportGen/LateStartReportGenWrapper.tsx
  17. +2
    -0
      src/components/LateStartReportGen/index.ts
  18. +201
    -0
      src/components/ReportSearchBox/SearchBox.tsx
  19. +3
    -0
      src/components/ReportSearchBox/index.ts
  20. +9
    -0
      src/components/utils/downloadExcel.ts
  21. +40
    -0
      src/components/utils/generateFakeData.ts

+ 3
- 1
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",


Двоичные данные
public/temp/AR01_Late Start Report.xlsx Просмотреть файл


+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Cost and Expense Report
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Delay Report
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Late Start Report
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Project Completion Report
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Project Completion Report with Outstanding Un-billed Hours
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 24
- 0
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 (
<I18nProvider namespaces={["analytics"]}>
<Typography variant="h4" marginInlineEnd={2}>
Resource Overconsumption Report
</Typography>
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}>
<ProgressCashFlowSearch/>
</Suspense> */}
<LateStartReportComponent />
</I18nProvider>
);
};
export default ProjectLateReport;

+ 3
- 1
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 <div>Analytics</div>;
};

export default Analytics;

+ 44
- 0
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",
},
];

+ 17
- 0
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 (
<Suspense fallback={<LateStartReportGen.Loading />}>
<LateStartReportGen />
</Suspense>
);
};

export default LateStartReport;

+ 2
- 0
src/components/LateStartReport/index.ts Просмотреть файл

@@ -0,0 +1,2 @@
//src\components\LateStartReport\index.ts
export { default } from "./LateStartReport";

+ 40
- 0
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 (
// <button onClick={handleDownload} disabled={isLoading}>
// {isLoading ? 'Generating...' : 'Download Report'}
// </button>
// );
// };

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 (
<button onClick={handleDownload}>
Download Report
</button>
);
};

+ 44
- 0
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<Omit<CashFlow, "id">>;
type SearchParamNames = keyof SearchQuery;

const ProgressByClientSearch: React.FC<Props> = ({ projects }) => {
const { t } = useTranslation("projects");

const searchCriteria: Criterion<SearchParamNames>[] = 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 (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log(query);
}}
/>
<DownloadReportButton />
</>
);
};

export default ProgressByClientSearch;

+ 41
- 0
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 (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default LateStartReportGenLoading;

+ 19
- 0
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 <LateStartReportGen projects={clentprojects} />;
};

LateStartReportGenWrapper.Loading = LateStartReportGenLoading;

export default LateStartReportGenWrapper;

+ 2
- 0
src/components/LateStartReportGen/index.ts Просмотреть файл

@@ -0,0 +1,2 @@
//src\components\LateStartReportGen\index.ts
export { default } from "./LateStartReportGenWrapper";

+ 201
- 0
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<T extends string> {
label: string;
label2?: string;
paramName: T;
paramName2?: T;
}

interface TextCriterion<T extends string> extends BaseCriterion<T> {
type: "text";
}

interface SelectCriterion<T extends string> extends BaseCriterion<T> {
type: "select";
options: string[];
}

interface DateRangeCriterion<T extends string> extends BaseCriterion<T> {
type: "dateRange";
}

export type Criterion<T extends string> =
| TextCriterion<T>
| SelectCriterion<T>
| DateRangeCriterion<T>;

interface Props<T extends string> {
criteria: Criterion<T>[];
onSearch: (inputs: Record<T, string>) => void;
onReset?: () => void;
}

function SearchBox<T extends string>({
criteria,
onSearch,
onReset,
}: Props<T>) {
const { t } = useTranslation("common");
const defaultInputs = useMemo(
() =>
criteria.reduce<Record<T, string>>(
(acc, c) => {
return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" };
},
{} as Record<T, string>,
),
[criteria],
);
const [inputs, setInputs] = useState(defaultInputs);

const makeInputChangeHandler = useCallback(
(paramName: T): React.ChangeEventHandler<HTMLInputElement> => {
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 (
<Card>
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Typography variant="overline">{t("Search Criteria")}</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
{criteria.map((c) => {
return (
<Grid key={c.paramName} item xs={6}>
{c.type === "text" && (
<TextField
label={c.label}
fullWidth
onChange={makeInputChangeHandler(c.paramName)}
value={inputs[c.paramName]}
/>
)}
{c.type === "select" && (
<FormControl fullWidth>
<InputLabel>{c.label}</InputLabel>
<Select
label={c.label}
onChange={makeSelectChangeHandler(c.paramName)}
value={inputs[c.paramName]}
>
<MenuItem value={"All"}>{t("All")}</MenuItem>
{c.options.map((option, index) => (
<MenuItem key={`${option}-${index}`} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
)}
{c.type === "dateRange" && (
<LocalizationProvider
dateAdapter={AdapterDayjs}
// TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
adapterLocale="zh-hk"
>
<Box display="flex">
<FormControl fullWidth>
<DatePicker
label={c.label}
onChange={makeDateChangeHandler(c.paramName)}
/>
</FormControl>
<Box
display="flex"
alignItems="center"
justifyContent="center"
marginInline={2}
>
{"-"}
</Box>
<FormControl fullWidth>
<DatePicker
label={c.label2}
onChange={makeDateToChangeHandler(c.paramName)}
/>
</FormControl>
</Box>
</LocalizationProvider>
)}
</Grid>
);
})}
</Grid>
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button
variant="text"
startIcon={<RestartAlt />}
onClick={handleReset}
>
{t("Reset")}
</Button>
<Button
variant="outlined"
startIcon={<Search />}
onClick={handleSearch}
>
{t("Search")}
</Button>
</CardActions>
</CardContent>
</Card>
);
}

export default SearchBox;

+ 3
- 0
src/components/ReportSearchBox/index.ts Просмотреть файл

@@ -0,0 +1,3 @@
//src\components\SearchBox\index.ts
export { default } from "./SearchBox";
export type { Criterion } from "./SearchBox";

+ 9
- 0
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');
};

+ 40
- 0
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;
};

Загрузка…
Отмена
Сохранить