diff --git a/public/temp/AR01_Late Start Report.xlsx b/public/temp/AR01_Late Start Report.xlsx index 24da97f..f93a8e8 100644 Binary files a/public/temp/AR01_Late Start Report.xlsx and b/public/temp/AR01_Late Start Report.xlsx differ diff --git a/public/temp/AR02_Delay Report.xlsx b/public/temp/AR02_Delay Report.xlsx index a8c53a8..4db1f65 100644 Binary files a/public/temp/AR02_Delay Report.xlsx and b/public/temp/AR02_Delay Report.xlsx differ diff --git a/public/temp/AR03_Resource Overconsumption.xlsx b/public/temp/AR03_Resource Overconsumption.xlsx new file mode 100644 index 0000000..013c31c Binary files /dev/null and b/public/temp/AR03_Resource Overconsumption.xlsx differ diff --git a/public/temp/AR04_Cost and Expense Report.xlsx b/public/temp/AR04_Cost and Expense Report.xlsx new file mode 100644 index 0000000..a414e1a Binary files /dev/null and b/public/temp/AR04_Cost and Expense Report.xlsx differ diff --git a/src/app/(main)/analytics/CostandExpenseReport/page.tsx b/src/app/(main)/analytics/CostandExpenseReport/page.tsx index 2f046c3..5cd328b 100644 --- a/src/app/(main)/analytics/CostandExpenseReport/page.tsx +++ b/src/app/(main)/analytics/CostandExpenseReport/page.tsx @@ -1,14 +1,14 @@ -//src\app\(main)\analytics\LateStartReport\page.tsx +//src\app\(main)\analytics\CostandExpenseReport\page.tsx import { Metadata } from "next"; import { I18nProvider } from "@/i18n"; import Typography from "@mui/material/Typography"; -import LateStartReportComponent from "@/components/LateStartReport"; +import CostandExpenseReportComponent from "@/components/Report/CostandExpenseReport"; export const metadata: Metadata = { title: "Project Status by Client", }; -const ProjectLateReport: React.FC = () => { +const CostandExpenseReport: React.FC = () => { return ( @@ -17,8 +17,8 @@ const ProjectLateReport: React.FC = () => { {/* }> */} - + ); }; -export default ProjectLateReport; +export default CostandExpenseReport; diff --git a/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx b/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx new file mode 100644 index 0000000..ee2c8ba --- /dev/null +++ b/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\ResourceOvercomsumptionReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import ResourceOverconsumptionReportComponent from "@/components/Report/ResourceOverconsumptionReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ResourceOverconsumptionReport: React.FC = () => { + return ( + + + Resource Overconsumption Report + + {/* }> + + */} + + + ); +}; +export default ResourceOverconsumptionReport; diff --git a/src/app/api/report3/index.ts b/src/app/api/report3/index.ts new file mode 100644 index 0000000..b2c8751 --- /dev/null +++ b/src/app/api/report3/index.ts @@ -0,0 +1,42 @@ +//src\app\api\report\index.ts +import { cache } from "react"; + +export interface ResourceOverconsumption { + 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 = () => { + fetchProjectsResourceOverconsumption(); +}; + +export const fetchProjectsResourceOverconsumption = cache(async () => { + return mockProjects; +}); + +const mockProjects: ResourceOverconsumption[] = [ + { + 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", + status: "1", + }, +]; diff --git a/src/app/api/report4/index.ts b/src/app/api/report4/index.ts new file mode 100644 index 0000000..5117519 --- /dev/null +++ b/src/app/api/report4/index.ts @@ -0,0 +1,42 @@ +//src\app\api\report\index.ts +import { cache } from "react"; + +export interface CostandExpense { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + remainPercent: string; +} + +export const preloadProjects = () => { + fetchProjectsCostandExpense(); +}; + +export const fetchProjectsCostandExpense = cache(async () => { + return mockProjects; +}); + +const mockProjects: CostandExpense[] = [ + { + 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", + remainPercent: "1", + }, +]; diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 14d2ed1..4f364e8 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -39,7 +39,7 @@ export const shortDateFormatter = (locale?: string) => { } }; -export function convertLocaleStringToNumber(numberString: String): number { +export function convertLocaleStringToNumber(numberString: string): number { const numberWithoutCommas = numberString.replace(/,/g, ""); return parseFloat(numberWithoutCommas); } diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index 61409b0..b8faa5e 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -61,7 +61,9 @@ const hasErrorsInTab = ( ) => { switch (tabIndex) { case 0: - return errors.projectName; + return ( + errors.projectName || errors.projectCode || errors.projectDescription + ); default: false; } @@ -101,7 +103,6 @@ const CreateProject: React.FC = ({ const onSubmit = useCallback>( async (data) => { try { - console.log(data); setServerError(""); await saveProject(data); router.replace("/projects"); @@ -115,7 +116,11 @@ const CreateProject: React.FC = ({ const onSubmitError = useCallback>( (errors) => { // Set the tab so that the focus will go there - if (errors.projectName) { + if ( + errors.projectName || + errors.projectDescription || + errors.projectCode + ) { setTabIndex(0); } }, diff --git a/src/components/CreateProject/MilestoneSection.tsx b/src/components/CreateProject/MilestoneSection.tsx index 9d18717..68a35a2 100644 --- a/src/components/CreateProject/MilestoneSection.tsx +++ b/src/components/CreateProject/MilestoneSection.tsx @@ -29,7 +29,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import StyledDataGrid from "../StyledDataGrid"; -import { moneyFormatter } from "@/app/utils/formatUtil"; +import { INPUT_DATE_FORMAT, moneyFormatter } from "@/app/utils/formatUtil"; import isDate from "lodash/isDate"; interface Props { @@ -206,7 +206,7 @@ const MilestoneSection: React.FC = ({ taskGroupId }) => { description: p.description!, id: p.id!, amount: p.amount!, - date: dayjs(p.date!).toISOString(), + date: dayjs(p.date!).format(INPUT_DATE_FORMAT), })), }, }); @@ -245,7 +245,7 @@ const MilestoneSection: React.FC = ({ taskGroupId }) => { ...milestones, [taskGroupId]: { ...milestones[taskGroupId], - startDate: date.toISOString(), + startDate: date.format(INPUT_DATE_FORMAT), }, }); }} @@ -264,7 +264,7 @@ const MilestoneSection: React.FC = ({ taskGroupId }) => { ...milestones, [taskGroupId]: { ...milestones[taskGroupId], - endDate: date.toISOString(), + endDate: date.format(INPUT_DATE_FORMAT), }, }); }} diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index db5b544..53dbb5c 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -103,8 +103,8 @@ const navigationItems: NavigationItem[] = [ {icon: , label:"Delay Report", path: "/analytics/DelayReport"}, {icon: , label:"Resource Overconsumption Report", path: "/analytics/ResourceOverconsumptionReport"}, {icon: , label:"Cost and Expense Report", path: "/analytics/CostandExpenseReport"}, - {icon: , label:"Completion Report", path: "/analytics/CompletionReport"}, - {icon: , label:"Completion Report with Outstanding Un-billed Hours Report", path: "/analytics/CompletionReportWO"}, + {icon: , label:"Completion Report", path: "/analytics/ProjectCompletionReport"}, + {icon: , label:"Completion Report with Outstanding Un-billed Hours Report", path: "/analytics/ProjectCompletionReportWO"}, ], }, { diff --git a/src/components/Report/CostandExpenseReport/CostandExpenseReport.tsx b/src/components/Report/CostandExpenseReport/CostandExpenseReport.tsx new file mode 100644 index 0000000..6c43a0a --- /dev/null +++ b/src/components/Report/CostandExpenseReport/CostandExpenseReport.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 CostandExpenseReportGen from "@/components/Report/CostandExpenseReportGen"; + +const CostandExpenseReport: React.FC = () => { + + return ( + }> + + + ); +}; + +export default CostandExpenseReport; \ No newline at end of file diff --git a/src/components/Report/CostandExpenseReport/index.ts b/src/components/Report/CostandExpenseReport/index.ts new file mode 100644 index 0000000..0dd1e51 --- /dev/null +++ b/src/components/Report/CostandExpenseReport/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReport\index.ts +export { default } from "./CostandExpenseReport"; diff --git a/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGen.tsx b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGen.tsx new file mode 100644 index 0000000..eea61cf --- /dev/null +++ b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGen.tsx @@ -0,0 +1,45 @@ +//src\components\LateStartReportGen\LateStartReportGen.tsx +"use client"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../../ReportSearchBox4"; +import { useTranslation } from "react-i18next"; +import { CostandExpense } from "@/app/api/report4"; + +interface Props { + projects: CostandExpense[]; +} +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: "Remaining Percentage", paramName: "remainPercent", type: "select", options: ["<50%", "50%-70%", ">70%"] }, + // { + // label: "Status", + // label2: "Remained Date To", + // paramName: "targetEndDate", + // type: "dateRange", + // }, + ], + [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + {/* */} + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenLoading.tsx b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenLoading.tsx new file mode 100644 index 0000000..9b0341d --- /dev/null +++ b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenLoading.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 DelayReportGenLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default DelayReportGenLoading; diff --git a/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenWrapper.tsx b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenWrapper.tsx new file mode 100644 index 0000000..b11b808 --- /dev/null +++ b/src/components/Report/CostandExpenseReportGen/CostandExpenseReportGenWrapper.tsx @@ -0,0 +1,19 @@ +//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx +import { fetchProjectsCostandExpense } from "@/app/api/report4"; +import React from "react"; +import CostandExpenseReportGen from "./CostandExpenseReportGen"; +import CostandExpenseReportGenLoading from "./CostandExpenseReportGenLoading"; + +interface SubComponents { + Loading: typeof CostandExpenseReportGenLoading; +} + +const CostandExpenseReportGenWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchProjectsCostandExpense(); + + return ; +}; + +CostandExpenseReportGenWrapper.Loading = CostandExpenseReportGenLoading; + +export default CostandExpenseReportGenWrapper; \ No newline at end of file diff --git a/src/components/Report/CostandExpenseReportGen/index.ts b/src/components/Report/CostandExpenseReportGen/index.ts new file mode 100644 index 0000000..0ef6085 --- /dev/null +++ b/src/components/Report/CostandExpenseReportGen/index.ts @@ -0,0 +1,2 @@ +//src\components\DelayReportGen\index.ts +export { default } from "./CostandExpenseReportGenWrapper"; diff --git a/src/components/Report/DelayReportGen/DelayReportGen.tsx b/src/components/Report/DelayReportGen/DelayReportGen.tsx index 4155613..8f8b10d 100644 --- a/src/components/Report/DelayReportGen/DelayReportGen.tsx +++ b/src/components/Report/DelayReportGen/DelayReportGen.tsx @@ -16,8 +16,8 @@ const ProgressByClientSearch: React.FC = ({ projects }) => { const searchCriteria: Criterion[] = useMemo( () => [ - { label: "Team", paramName: "team", type: "text" }, - { label: "Client", paramName: "client", type: "text" }, + { 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: ["Delayed", "Potential Delay"] }, // { // label: "Status", diff --git a/src/components/Report/LateStartReportGen/LateStartReportGen.tsx b/src/components/Report/LateStartReportGen/LateStartReportGen.tsx index 81ff1a0..2fca827 100644 --- a/src/components/Report/LateStartReportGen/LateStartReportGen.tsx +++ b/src/components/Report/LateStartReportGen/LateStartReportGen.tsx @@ -5,6 +5,9 @@ import SearchBox, { Criterion } from "../../ReportSearchBox"; import { useTranslation } from "react-i18next"; import { LateStart } from "@/app/api/report"; //import { DownloadReportButton } from './DownloadReportButton'; +import axios from 'axios'; +import { apiPath } from '../../../auth/utils'; +//import { GET_QC_CATEGORY_COMBO } from 'utils/ApiPathConst'; interface Props { projects: LateStart[]; } @@ -13,11 +16,19 @@ type SearchParamNames = keyof SearchQuery; const ProgressByClientSearch: React.FC = ({ projects }) => { const { t } = useTranslation("projects"); - + // const [teamCombo, setteamCombo] = useState([]); + // const getteamCombo = () => { + // axios.get(`${apiPath}${GET_QC_CATEGORY_COMBO}`) + // .then(response => { + // setteamCombo(response.data.records)}) + // .catch(error => { + // console.error('Error fetching data: ', error); + // }); + // } const searchCriteria: Criterion[] = useMemo( () => [ - { label: "Team", paramName: "team", type: "text" }, - { label: "Client", paramName: "client", type: "text" }, + { label: "Team", paramName: "team", type: "select", options: ["AAA", "BBB", "CCC"] }, + { label: "Client", paramName: "client", type: "select", options: ["Cust A", "Cust B", "Cust C"] }, { label: "Remained Date From", label2: "Remained Date To", diff --git a/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx b/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx new file mode 100644 index 0000000..345b2f2 --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReport/ResourceOverconsumptionReport.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 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 new file mode 100644 index 0000000..ce20324 --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReport/index.ts @@ -0,0 +1,2 @@ +//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 new file mode 100644 index 0000000..0faf37e --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx @@ -0,0 +1,45 @@ +//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/ResourceOverconsumptionReportGenLoading.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenLoading.tsx new file mode 100644 index 0000000..9ae7417 --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenLoading.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 ResourceOvercomsumptionReportGenLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ResourceOvercomsumptionReportGenLoading; diff --git a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx new file mode 100644 index 0000000..a93f64b --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx @@ -0,0 +1,19 @@ +//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 new file mode 100644 index 0000000..82fb633 --- /dev/null +++ b/src/components/Report/ResourceOverconsumptionReportGen/index.ts @@ -0,0 +1,2 @@ +//src\components\DelayReportGen\index.ts +export { default } from "./ResourceOverconsumptionReportGenWrapper"; diff --git a/src/components/ReportSearchBox/SearchBox.tsx b/src/components/ReportSearchBox/SearchBox.tsx index 6ca34f1..c629ad4 100644 --- a/src/components/ReportSearchBox/SearchBox.tsx +++ b/src/components/ReportSearchBox/SearchBox.tsx @@ -253,6 +253,7 @@ function SearchBox({ ({ @@ -287,9 +289,9 @@ function SearchBox({ diff --git a/src/components/ReportSearchBox2/SearchBox2.tsx b/src/components/ReportSearchBox2/SearchBox2.tsx index 16b72df..37d65ba 100644 --- a/src/components/ReportSearchBox2/SearchBox2.tsx +++ b/src/components/ReportSearchBox2/SearchBox2.tsx @@ -253,6 +253,7 @@ function SearchBox({ ({ @@ -287,9 +289,9 @@ function SearchBox({ diff --git a/src/components/ReportSearchBox3/SearchBox3.tsx b/src/components/ReportSearchBox3/SearchBox3.tsx new file mode 100644 index 0000000..8bf2fea --- /dev/null +++ b/src/components/ReportSearchBox3/SearchBox3.tsx @@ -0,0 +1,302 @@ +//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 }); + + // 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 + }); + + // 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 } } + } + }; + } + } + + // 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" && ( + + + + + + + {"-"} + + + + + + + )} + + ); + })} + + + + + + + + ); +} + +export default SearchBox; diff --git a/src/components/ReportSearchBox3/index.ts b/src/components/ReportSearchBox3/index.ts new file mode 100644 index 0000000..d481fbd --- /dev/null +++ b/src/components/ReportSearchBox3/index.ts @@ -0,0 +1,3 @@ +//src\components\SearchBox\index.ts +export { default } from "./SearchBox3"; +export type { Criterion } from "./SearchBox3"; diff --git a/src/components/ReportSearchBox4/SearchBox4.tsx b/src/components/ReportSearchBox4/SearchBox4.tsx new file mode 100644 index 0000000..8b9dd4d --- /dev/null +++ b/src/components/ReportSearchBox4/SearchBox4.tsx @@ -0,0 +1,302 @@ +//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/AR04_Cost and Expense 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 }); + + // 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 + }); + + // 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 J6 + // Apply styles from A6 to J6 (bold, bottom border, center alignment) + for (let col = 0; col < 10; 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 } } + } + }; + } + } + + // 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 = `AR04_Cost_and_Expense_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/ReportSearchBox4/index.ts b/src/components/ReportSearchBox4/index.ts new file mode 100644 index 0000000..0e518ff --- /dev/null +++ b/src/components/ReportSearchBox4/index.ts @@ -0,0 +1,3 @@ +//src\components\SearchBox\index.ts +export { default } from "./SearchBox4"; +export type { Criterion } from "./SearchBox4"; diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index fe058f7..13b03ee 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -154,6 +154,7 @@ function SearchBox({ ({ diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index 4c82280..bfecd36 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -11,7 +11,7 @@ import TablePagination, { TablePaginationProps, } from "@mui/material/TablePagination"; import TableRow from "@mui/material/TableRow"; -import IconButton from "@mui/material/IconButton"; +import IconButton, { IconButtonOwnProps, IconButtonPropsColorOverrides } from "@mui/material/IconButton"; export interface ResultWithId { id: string | number; @@ -20,6 +20,7 @@ export interface ResultWithId { interface BaseColumn { name: keyof T; label: string; + color?: IconButtonOwnProps["color"]; } interface ColumnWithAction extends BaseColumn { @@ -91,7 +92,7 @@ function SearchResults({ {isActionColumn(column) ? ( column.onClick(item)} > {column.buttonIcon} diff --git a/src/components/TimesheetTable/EntryInputTable.tsx b/src/components/TimesheetTable/EntryInputTable.tsx index 57a18b3..7d9e9de 100644 --- a/src/components/TimesheetTable/EntryInputTable.tsx +++ b/src/components/TimesheetTable/EntryInputTable.tsx @@ -22,6 +22,9 @@ import { AssignedProject } from "@/app/api/projects"; import uniqBy from "lodash/uniqBy"; import { TaskGroup } from "@/app/api/tasks"; import dayjs from "dayjs"; +import isBetween from "dayjs/plugin/isBetween"; + +dayjs.extend(isBetween); const mockProjects: AssignedProject[] = [ {