diff --git a/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx b/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx index a1751be..771d21f 100644 --- a/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx +++ b/src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx @@ -1,24 +1,28 @@ -//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"; +import { Suspense } from "react"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import ResourceOverconsumptionReport from "@/components/ResourceOverconsumptionReport"; +import { Typography } from "@mui/material"; export const metadata: Metadata = { - title: "Resource Overconsumption Report", + title: "Staff Monthly Work Hours Analysis Report", }; -const ResourceOverconsumptionReport: React.FC = () => { - return ( - - - Resource Overconsumption Report - - {/* }> - - */} - - - ); +const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => { + const { t } = await getServerI18n("User Group"); + + return ( + <> + + {t("Project Resource Overconsumption Report")} + + + }> + + + + + ); }; -export default ResourceOverconsumptionReport; + +export default StaffMonthlyWorkHoursAnalysisReport; diff --git a/src/app/api/report3/index.ts b/src/app/api/report3/index.ts deleted file mode 100644 index b2c8751..0000000 --- a/src/app/api/report3/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -//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/reports/actions.ts b/src/app/api/reports/actions.ts index 3d49a16..a4c2b85 100644 --- a/src/app/api/reports/actions.ts +++ b/src/app/api/reports/actions.ts @@ -1,7 +1,7 @@ "use server"; import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil"; -import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest,LateStartReportRequest } from "."; +import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest,LateStartReportRequest, ProjectResourceOverconsumptionReportRequest } from "."; import { BASE_API_URL } from "@/config/api"; export interface FileResponse { @@ -35,6 +35,19 @@ export const fetchMonthlyWorkHoursReport = async (data: MonthlyWorkHoursReportRe return reportBlob }; +export const fetchProjectResourceOverconsumptionReport = async (data: ProjectResourceOverconsumptionReportRequest) => { + const reportBlob = await serverFetchBlob( + `${BASE_API_URL}/reports/ProjectResourceOverconsumptionReport`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + + return reportBlob +}; + // export const fetchLateStartReport = async (data: LateStartReportRequest) => { // const response = await serverFetchBlob( // `${BASE_API_URL}/reports/downloadLateStartReport`, diff --git a/src/app/api/reports/index.ts b/src/app/api/reports/index.ts index dd1afb8..f1fa613 100644 --- a/src/app/api/reports/index.ts +++ b/src/app/api/reports/index.ts @@ -25,6 +25,20 @@ export interface MonthlyWorkHoursReportRequest { id: number; yearMonth: string; } +// - Project Resource Overconsumption Report +export interface ProjectResourceOverconsumptionReportFilter { + team: string[]; + customer: string[]; + status: string[]; + lowerLimit: number; +} + +export interface ProjectResourceOverconsumptionReportRequest { + teamId?: number + custId?: number + status: "All" | "Within Budget" | "Potential Overconsumption" | "Overconsumption" + lowerLimit: number +} export interface LateStartReportFilter { remainedDays: number; diff --git a/src/components/Report/ReportSearchBox3/SearchBox3.tsx b/src/components/Report/ReportSearchBox3/SearchBox3.tsx deleted file mode 100644 index aafa7d0..0000000 --- a/src/components/Report/ReportSearchBox3/SearchBox3.tsx +++ /dev/null @@ -1,313 +0,0 @@ -//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 }); - - // 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 } } - } - }; - } - } - - const firstTableData = [ - ['Column1', 'Column2', 'Column3'], // Row 1 - ['Data1', 'Data2', 'Data3'], // Row 2 - // ... 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++; - } - - // 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 = `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/Report/ReportSearchBox3/index.ts b/src/components/Report/ReportSearchBox3/index.ts deleted file mode 100644 index d481fbd..0000000 --- a/src/components/Report/ReportSearchBox3/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -//src\components\SearchBox\index.ts -export { default } from "./SearchBox3"; -export type { Criterion } from "./SearchBox3"; diff --git a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx deleted file mode 100644 index a6ec216..0000000 --- a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGen.tsx +++ /dev/null @@ -1,45 +0,0 @@ -//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/ResourceOverconsumptionReportGenWrapper.tsx b/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx deleted file mode 100644 index a93f64b..0000000 --- a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenWrapper.tsx +++ /dev/null @@ -1,19 +0,0 @@ -//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 deleted file mode 100644 index 82fb633..0000000 --- a/src/components/Report/ResourceOverconsumptionReportGen/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -//src\components\DelayReportGen\index.ts -export { default } from "./ResourceOverconsumptionReportGenWrapper"; diff --git a/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx new file mode 100644 index 0000000..5e6e76a --- /dev/null +++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx @@ -0,0 +1,96 @@ +"use client"; +import React, { useMemo } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import { ProjectResult } from "@/app/api/projects"; +import { fetchMonthlyWorkHoursReport, fetchProjectCashFlowReport, fetchProjectResourceOverconsumptionReport } from "@/app/api/reports/actions"; +import { downloadFile } from "@/app/utils/commonUtil"; +import { BASE_API_URL } from "@/config/api"; +import { ProjectResourceOverconsumptionReportFilter, ProjectResourceOverconsumptionReportRequest } from "@/app/api/reports"; +import { StaffResult } from "@/app/api/staff"; +import { TeamResult } from "@/app/api/team"; +import { Customer } from "@/app/api/customer"; + +interface Props { + team: TeamResult[] + customer: Customer[] +} + +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ResourceOverconsumptionReport: React.FC = ({ team, customer }) => { + const { t } = useTranslation(); + const teamCombo = team.map(t => `${t.name} - ${t.code}`) + const custCombo = customer.map(c => `${c.name} - ${c.code}`) + const statusCombo = ["Within Budget, Overconsumption", "Potential Overconsumption"] + // const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`) + // console.log(staffs) + + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("Team"), + paramName: "team", + type: "select", + options: teamCombo, + needAll: true + }, + { + label: t("Client"), + paramName: "customer", + type: "select", + options: custCombo, + needAll: true + }, + { + label: t("Status"), + paramName: "status", + type: "select", + options: statusCombo, + needAll: true + }, + { + label: t("lowerLimit"), + paramName: "lowerLimit", + type: "number", + }, + ], + [t], + ); + +return ( + <> + { + let index = 0 + let postData: ProjectResourceOverconsumptionReportRequest = { + status: "All", + lowerLimit: 0.9 + } + if (query.team.length > 0 && query.team.toLocaleLowerCase() !== "all") { + index = teamCombo.findIndex(team => team === query.team) + postData.teamId = team[index].id + } + if (query.customer.length > 0 && query.customer.toLocaleLowerCase() !== "all") { + index = custCombo.findIndex(customer => customer === query.customer) + postData.custId = customer[index].id + } + if (Boolean(query.lowerLimit)) { + postData.lowerLimit = query.lowerLimit/100 + } + postData.status = query.status + console.log(postData) + const response = await fetchProjectResourceOverconsumptionReport(postData) + if (response) { + downloadFile(new Uint8Array(response.blobValue), response.filename!!) + } + } + } + /> + + ) +} + +export default ResourceOverconsumptionReport \ No newline at end of file diff --git a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenLoading.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx similarity index 89% rename from src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenLoading.tsx rename to src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx index 9ae7417..945d13c 100644 --- a/src/components/Report/ResourceOverconsumptionReportGen/ResourceOverconsumptionReportGenLoading.tsx +++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportLoading.tsx @@ -6,7 +6,7 @@ import Stack from "@mui/material/Stack"; import React from "react"; // Can make this nicer -export const ResourceOvercomsumptionReportGenLoading: React.FC = () => { +export const ResourceOvercomsumptionReportLoading: React.FC = () => { return ( <> @@ -38,4 +38,4 @@ export const ResourceOvercomsumptionReportGenLoading: React.FC = () => { ); }; -export default ResourceOvercomsumptionReportGenLoading; +export default ResourceOvercomsumptionReportLoading; diff --git a/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx new file mode 100644 index 0000000..1ab9d24 --- /dev/null +++ b/src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReportWrapper.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import ResourceOvercomsumptionReportLoading from "./ResourceOverconsumptionReportLoading"; +import ResourceOverconsumptionReport from "./ResourceOverconsumptionReport"; +import { fetchAllCustomers } from "@/app/api/customer"; +import { fetchTeam } from "@/app/api/team"; + +interface SubComponents { + Loading: typeof ResourceOvercomsumptionReportLoading; +} + +const ResourceOvercomsumptionReportWrapper: React.FC & SubComponents = async () => { + const customers = await fetchAllCustomers() + const teams = await fetchTeam () + + return ; +}; + +ResourceOvercomsumptionReportWrapper.Loading = ResourceOvercomsumptionReportLoading; + +export default ResourceOvercomsumptionReportWrapper; \ No newline at end of file diff --git a/src/components/ResourceOverconsumptionReport/index.ts b/src/components/ResourceOverconsumptionReport/index.ts new file mode 100644 index 0000000..b5f20e2 --- /dev/null +++ b/src/components/ResourceOverconsumptionReport/index.ts @@ -0,0 +1 @@ +export { default } from "./ResourceOverconsumptionReportWrapper"; \ No newline at end of file diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 686eb8d..2ab52b0 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -4,7 +4,7 @@ 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 React, { FocusEvent, KeyboardEvent, PointerEvent, useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import TextField from "@mui/material/TextField"; import FormControl from "@mui/material/FormControl"; @@ -15,7 +15,7 @@ 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 FileDownload from '@mui/icons-material/FileDownload'; +import FileDownload from "@mui/icons-material/FileDownload"; import dayjs from "dayjs"; import "dayjs/locale/zh-hk"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -23,6 +23,17 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { Box } from "@mui/material"; import { DateCalendar } from "@mui/x-date-pickers"; +import { + Unstable_NumberInput as BaseNumberInput, + NumberInputProps, + numberInputClasses, +} from "@mui/base/Unstable_NumberInput"; +import { + StyledButton, + StyledInputElement, + StyledInputRoot, +} from "@/theme/colorConst"; +import { InputAdornment, NumberInput } from "../utils/numberInput"; interface BaseCriterion { label: string; @@ -49,17 +60,22 @@ interface MonthYearCriterion extends BaseCriterion { type: "monthYear"; } +interface NumberCriterion extends BaseCriterion { + type: "number"; +} + export type Criterion = | TextCriterion | SelectCriterion | DateRangeCriterion - | MonthYearCriterion; + | MonthYearCriterion + | NumberCriterion; interface Props { criteria: Criterion[]; onSearch: (inputs: Record) => void; onReset?: () => void; - formType?: String, + formType?: String; } function SearchBox({ @@ -75,10 +91,14 @@ function SearchBox({ (acc, c) => { return { ...acc, - [c.paramName]: c.type === "select" ? - !(c.needAll === false) ? "All" : - c.options.length > 0 ? c.options[0] : "" - : "" + [c.paramName]: + c.type === "select" + ? !(c.needAll === false) + ? "All" + : c.options.length > 0 + ? c.options[0] + : "" + : "", }; }, {} as Record @@ -86,7 +106,7 @@ function SearchBox({ [criteria] ); const [inputs, setInputs] = useState(defaultInputs); - + const makeInputChangeHandler = useCallback( (paramName: T): React.ChangeEventHandler => { return (e) => { @@ -95,6 +115,15 @@ function SearchBox({ }, [] ); + + const makeNumberChangeHandler = useCallback( + (paramName: T): (event: FocusEvent | PointerEvent | KeyboardEvent, value: number | null) => void => { + return (event, value) => { + setInputs((i) => ({ ...i, [paramName]: value })); + }; + }, + [] + ); const makeSelectChangeHandler = useCallback((paramName: T) => { return (e: SelectChangeEvent) => { @@ -110,7 +139,7 @@ function SearchBox({ const makeMonthYearChangeHandler = useCallback((paramName: T) => { return (e: any) => { - console.log(dayjs(e).format("YYYY-MM")) + console.log(dayjs(e).format("YYYY-MM")); setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM") })); }; }, []); @@ -168,6 +197,15 @@ function SearchBox({ )} + {c.type === "number" && ( + %} + /> + )} {c.type === "monthYear" && ( ({