@@ -21,6 +21,7 @@ | |||||
"@mui/x-date-pickers": "^6.18.7", | "@mui/x-date-pickers": "^6.18.7", | ||||
"@unly/universal-language-detector": "^2.0.3", | "@unly/universal-language-detector": "^2.0.3", | ||||
"apexcharts": "^3.45.2", | "apexcharts": "^3.45.2", | ||||
"axios": "^1.9.0", | |||||
"dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
"i18next": "^23.7.11", | "i18next": "^23.7.11", | ||||
"i18next-resources-to-backend": "^1.2.0", | "i18next-resources-to-backend": "^1.2.0", | ||||
@@ -28,6 +29,7 @@ | |||||
"next": "14.0.4", | "next": "14.0.4", | ||||
"next-auth": "^4.24.5", | "next-auth": "^4.24.5", | ||||
"next-pwa": "^5.6.0", | "next-pwa": "^5.6.0", | ||||
"qs": "^6.14.0", | |||||
"react": "^18", | "react": "^18", | ||||
"react-apexcharts": "^1.4.1", | "react-apexcharts": "^1.4.1", | ||||
"react-dom": "^18", | "react-dom": "^18", | ||||
@@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography"; | |||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import Link from "next/link"; | import Link from "next/link"; | ||||
import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
import RoughScheduleLoading from "@/components/RoughScheduleSetting/RoughScheduleLoading"; | |||||
import RoughScheduleSetting from "@/components/RoughScheduleSetting/RoughScheduleSetting"; | |||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Rough Schedule Setting", | title: "Rough Schedule Setting", | ||||
@@ -38,8 +40,8 @@ const roughScheduleSetting: React.FC = async () => { | |||||
{t("Create product")} | {t("Create product")} | ||||
</Button> */} | </Button> */} | ||||
</Stack> | </Stack> | ||||
<Suspense fallback={<ItemsSearch.Loading />}> | |||||
<ItemsSearch /> | |||||
<Suspense fallback={<RoughScheduleLoading.Loading />}> | |||||
<RoughScheduleSetting /> | |||||
</Suspense> | </Suspense> | ||||
</> | </> | ||||
); | ); | ||||
@@ -0,0 +1,40 @@ | |||||
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 RoughScheduleLoading: 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 RoughScheduleLoading; |
@@ -1,191 +1,185 @@ | |||||
"use client"; | "use client"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import React, {useCallback, useEffect, useMemo, useState} from "react"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { ItemsResult} from "@/app/api/settings/item"; | |||||
import SearchResults, { Column } from "../SearchResults"; | |||||
import { EditNote } from "@mui/icons-material"; | |||||
import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
import { | |||||
FormProvider, | |||||
SubmitErrorHandler, | |||||
SubmitHandler, | |||||
useForm, | |||||
} from "react-hook-form"; | |||||
import { deleteDialog } from "../Swal/CustomAlerts"; | |||||
import { Box, Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||||
import { Check, Close, EditNote } from "@mui/icons-material"; | |||||
import { useGridApiRef } from "@mui/x-data-grid"; | |||||
import {CreateItemInputs, saveItem} from "@/app/api/settings/item/actions"; | |||||
import {saveItemQcChecks} from "@/app/api/settings/qcCheck/actions"; | |||||
import {useTranslation} from "react-i18next/index"; | |||||
import {ItemQc} from "@/app/api/settings/item"; | |||||
import { GridDeleteIcon } from "@mui/x-data-grid"; | |||||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
import axios from "axios"; | |||||
import {BASE_API_URL, NEXT_PUBLIC_API_URL} from "@/config/api"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||||
import Qs from 'qs'; // Make sure to import Qs | |||||
type Props = { | type Props = { | ||||
isEditMode: boolean; | |||||
// type: TypeEnum; | |||||
defaultValues: Partial<CreateItemInputs> | undefined; | |||||
qcChecks: ItemQc[] | |||||
items: ItemsResult[]; | |||||
}; | }; | ||||
type SearchQuery = Partial<Omit<ItemsResult, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const CreateItem: React.FC<Props> = ({ | |||||
isEditMode, | |||||
// type, | |||||
defaultValues, | |||||
qcChecks | |||||
}) => { | |||||
// console.log(type) | |||||
const apiRef = useGridApiRef(); | |||||
const params = useSearchParams() | |||||
console.log(params.get("id")) | |||||
const [serverError, setServerError] = useState<String>(""); | |||||
const [tabIndex, setTabIndex] = useState(0); | |||||
const { t } = useTranslation(); | |||||
const RSSOverview: React.FC<Props> = ({ items }) => { | |||||
const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items ?? []); | |||||
const { t } = useTranslation("items"); | |||||
const router = useRouter(); | const router = useRouter(); | ||||
const title = "Product / Material" | |||||
const [filterObj, setFilterObj] = useState({}); | |||||
const [tempSelectedValue, setTempSelectedValue] = useState({}); | |||||
const [pagingController, setPagingController] = useState({ | |||||
pageNum: 1, | |||||
pageSize: 10, | |||||
totalCount: 0, | |||||
}) | |||||
const [mode, redirPath] = useMemo(() => { | const [mode, redirPath] = useMemo(() => { | ||||
// var typeId = TypeEnum.CONSUMABLE_ID | // var typeId = TypeEnum.CONSUMABLE_ID | ||||
var title = ""; | var title = ""; | ||||
var mode = ""; | |||||
var mode = "Search"; | |||||
var redirPath = ""; | var redirPath = ""; | ||||
// if (type === TypeEnum.MATERIAL) { | |||||
// typeId = TypeEnum.MATERIAL_ID | |||||
// title = "Material"; | |||||
// redirPath = "/settings/material"; | |||||
// } | |||||
// if (type === TypeEnum.PRODUCT) { | |||||
// typeId = TypeEnum.PRODUCT_ID | |||||
title = "Rough Schedule"; | |||||
title = "Product"; | |||||
redirPath = "/settings/rss"; | redirPath = "/settings/rss"; | ||||
// } | |||||
// if (type === TypeEnum.BYPRODUCT) { | |||||
// typeId = TypeEnum.BYPRODUCT_ID | |||||
// title = "By-Product"; | |||||
// redirPath = "/settings/byProduct"; | |||||
// } | |||||
if (isEditMode) { | |||||
mode = "Edit"; | |||||
} else { | |||||
mode = "Create"; | |||||
} | |||||
return [mode, redirPath]; | return [mode, redirPath]; | ||||
}, [isEditMode]); | |||||
// console.log(typeId) | |||||
const formProps = useForm<CreateItemInputs>({ | |||||
defaultValues: defaultValues ? defaultValues : { | |||||
}, | |||||
}); | |||||
const errors = formProps.formState.errors; | |||||
}, []); | |||||
const handleCancel = () => { | |||||
router.replace(`/settings/product`); | |||||
const handleSelectionChange = (selectedValues: string[]) => { | |||||
setTempSelectedValue({ | |||||
...tempSelectedValue, | |||||
excludeDate: selectedValues, | |||||
}); | |||||
}; | }; | ||||
const onSubmit = useCallback<SubmitHandler<CreateItemInputs & {}>>( | |||||
async (data, event) => { | |||||
let hasErrors = false; | |||||
console.log(errors) | |||||
// console.log(apiRef.current.getCellValue(2, "lowerLimit")) | |||||
// apiRef.current. | |||||
try { | |||||
if (hasErrors) { | |||||
setServerError(t("An error has occurred. Please try again later.") as String); | |||||
return false; | |||||
} | |||||
console.log("data posted"); | |||||
console.log(data); | |||||
const qcCheck = data.qcChecks.length > 0 ? data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!)).map((qc) => { | |||||
return { | |||||
qcItemId: qc.id, | |||||
instruction: qc.instruction, | |||||
lowerLimit: qc.lowerLimit, | |||||
upperLimit: qc.upperLimit, | |||||
itemId: parseInt(params.get("id")!.toString()) | |||||
} | |||||
}) : [] | |||||
const test = data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!)) | |||||
// TODO: | |||||
// 1. check field ( directly modify col def / check here ) | |||||
// 2. set error change tab index | |||||
console.log(test) | |||||
console.log(qcCheck) | |||||
// return | |||||
// do api | |||||
console.log("asdad") | |||||
var responseI = await saveItem(data); | |||||
console.log("asdad") | |||||
var responseQ = await saveItemQcChecks(qcCheck) | |||||
if (responseI && responseQ) { | |||||
if (!Boolean(responseI.id)) { | |||||
formProps.setError(responseI.errorPosition!! as keyof CreateItemInputs, { | |||||
message: responseI.message!!, | |||||
type: "required", | |||||
}); | |||||
} else if (!Boolean(responseQ.id)) { | |||||
} else if (Boolean(responseI.id) && Boolean(responseQ.id)) { | |||||
router.replace(redirPath); | |||||
} | |||||
} | |||||
} catch (e) { | |||||
// backend error | |||||
setServerError(t("An error has occurred. Please try again later.")); | |||||
console.log(e); | |||||
} | |||||
const dayOptions = [ | |||||
{label: "Mon", value: 1}, | |||||
{label: "Tue", value: 2}, | |||||
{label: "Wed", value: 3}, | |||||
{label: "Thu", value: 4}, | |||||
{label: "Fri", value: 5}, | |||||
{label: "Sat", value: 6}, | |||||
{label: "Sun", value: 7}, | |||||
]; | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
() => { | |||||
var searchCriteria: Criterion<SearchParamNames>[] = [ | |||||
{ label: t("Finished Goods Name"), paramName: "fgName", type: "text" }, | |||||
{ | |||||
label: t("Exclude Date"), | |||||
paramName: "excludeDate", | |||||
type: "multi-select", | |||||
options: dayOptions, | |||||
selectedValues: filterObj, | |||||
handleSelectionChange: handleSelectionChange, | |||||
}, | |||||
] | |||||
return searchCriteria | |||||
}, | }, | ||||
[apiRef, router, t] | |||||
[t, items] | |||||
); | ); | ||||
// multiple tabs | |||||
const onSubmitError = useCallback<SubmitErrorHandler<CreateItemInputs>>( | |||||
(errors) => {}, | |||||
[] | |||||
const onDetailClick = useCallback( | |||||
(item: ItemsResult) => { | |||||
router.push(`/settings/items/edit?id=${item.id}`); | |||||
}, | |||||
[router] | |||||
); | |||||
const onDeleteClick = useCallback( | |||||
(item: ItemsResult) => {}, | |||||
[router] | |||||
); | ); | ||||
const columns = useMemo<Column<ItemsResult>[]>( | |||||
() => [ | |||||
{ | |||||
name: "id", | |||||
label: t("Details"), | |||||
onClick: onDetailClick, | |||||
buttonIcon: <EditNote />, | |||||
}, | |||||
{ | |||||
name: "fgName", | |||||
label: "Finished Goods Name", | |||||
}, | |||||
{ | |||||
name: "excludeDate", | |||||
label: t("Exclude Date"), | |||||
}, | |||||
{ | |||||
name: "action", | |||||
label: t(""), | |||||
buttonIcon: <GridDeleteIcon />, | |||||
onClick: onDeleteClick, | |||||
}, | |||||
], | |||||
[filteredItems] | |||||
); | |||||
useEffect(() => { | |||||
refetchData(filterObj); | |||||
}, [filterObj, pagingController.pageNum, pagingController.pageSize]); | |||||
const refetchData = async (filterObj: SearchQuery) => { | |||||
const authHeader = axiosInstance.defaults.headers['Authorization']; | |||||
if (!authHeader) { | |||||
return; // Exit the function if the token is not set | |||||
} | |||||
const params ={ | |||||
pageNum: pagingController.pageNum, | |||||
pageSize: pagingController.pageSize, | |||||
...filterObj, | |||||
...tempSelectedValue, | |||||
} | |||||
try { | |||||
const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, { | |||||
params, | |||||
paramsSerializer: (params) => { | |||||
return Qs.stringify(params, { arrayFormat: 'repeat' }); | |||||
}, | |||||
}); | |||||
setFilteredItems(response.data.records); | |||||
setPagingController({ | |||||
...pagingController, | |||||
totalCount: response.data.total | |||||
}) | |||||
return response; // Return the data from the response | |||||
} catch (error) { | |||||
console.error('Error fetching items:', error); | |||||
throw error; // Rethrow the error for further handling | |||||
} | |||||
}; | |||||
const onReset = useCallback(() => { | |||||
//setFilteredItems(items ?? []); | |||||
setFilterObj({}); | |||||
setTempSelectedValue({}); | |||||
refetchData(); | |||||
}, [items]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | |||||
<Stack | |||||
spacing={2} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
> | |||||
<Grid> | |||||
<Typography mb={2} variant="h4"> | |||||
{t(`${mode} ${title}`)} | |||||
</Typography> | |||||
</Grid> | |||||
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
<Tab label={t("Product / Material Details")} iconPosition="end"/> | |||||
<Tab label={t("Qc items")} iconPosition="end" /> | |||||
</Tabs> | |||||
{serverError && ( | |||||
<Typography variant="body2" color="error" alignSelf="flex-end"> | |||||
{serverError} | |||||
</Typography> | |||||
)} | |||||
{tabIndex === 0 && <ProductDetails />} | |||||
{tabIndex === 1 && <QcDetails apiRef={apiRef} />} | |||||
{/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} | |||||
{/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
name="submit" | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
// disabled={submitDisabled} | |||||
> | |||||
{isEditMode ? t("Save") as String : t("Confirm") as String} | |||||
</Button> | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<Close />} | |||||
onClick={handleCancel} | |||||
> | |||||
{t("Cancel") as String} | |||||
</Button> | |||||
</Stack> | |||||
</Stack> | |||||
</FormProvider> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={(query) => { | |||||
setFilterObj({ | |||||
...query | |||||
}) | |||||
}} | |||||
onReset={onReset} | |||||
/> | |||||
<SearchResults<ItemsResult> | |||||
items={filteredItems} | |||||
columns={columns} | |||||
setPagingController={setPagingController} | |||||
pagingController={pagingController} | |||||
isAutoPaging={false} | |||||
/> | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
export default CreateItem; | |||||
export default RSSOverview; |
@@ -1,51 +1,23 @@ | |||||
import {CreateItemInputs} from "@/app/api/settings/item/actions"; | |||||
import CreateItemLoading from "../CreateItem/CreateItemLoading"; | |||||
import {fetchItem} from "@/app/api/settings/item"; | |||||
import CreateItem from "@/components/RoughScheduleSetting/RoughScheduleSetting"; | |||||
import { fetchAllItems, } from "@/app/api/settings/item"; | |||||
import {RoughScheduleLoading} from "./RoughScheduleLoading"; | |||||
import RSSOverview from "@/components/RoughScheduleSetting/RoughScheduleSetting"; | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof CreateItemLoading; | |||||
Loading: typeof RoughScheduleLoading; | |||||
} | } | ||||
type Props = { | type Props = { | ||||
id?: number | |||||
// type: TypeEnum; | // type: TypeEnum; | ||||
}; | }; | ||||
const RoughScheduleSettingWrapper: ({id}: { id: any }) => Promise<JSX.Element> = async ({ id }) => { | |||||
var result | |||||
var defaultValues: Partial<CreateItemInputs> | undefined | |||||
const RoughScheduleSettingWrapper: ({}: {}) => Promise<JSX.Element> = async ({ | |||||
// type, | |||||
}) => { | |||||
// console.log(type) | // console.log(type) | ||||
var qcChecks | |||||
if (id) { | |||||
result = await fetchItem(id); | |||||
const item = result.item | |||||
qcChecks = result.qcChecks | |||||
const activeRows = qcChecks.filter(it => it.isActive).map(i => i.id) | |||||
console.log(qcChecks) | |||||
defaultValues = { | |||||
type: item?.type, | |||||
id: item?.id, | |||||
code: item?.code, | |||||
name: item?.name, | |||||
description: item?.description, | |||||
remarks: item?.remarks, | |||||
shelfLife: item?.shelfLife, | |||||
countryOfOrigin: item?.countryOfOrigin, | |||||
maxQty: item?.maxQty, | |||||
qcChecks: qcChecks, | |||||
qcChecks_active: activeRows | |||||
}; | |||||
} | |||||
return ( | |||||
<CreateItem | |||||
isEditMode={Boolean(id)} | |||||
defaultValues={defaultValues} | |||||
qcChecks={qcChecks || []} | |||||
/> | |||||
); | |||||
var result = await fetchAllItems() | |||||
return <RSSOverview items={result} />; | |||||
}; | }; | ||||
RoughScheduleSettingWrapper.Loading = CreateItemLoading; | |||||
RoughScheduleSettingWrapper.Loading = RoughScheduleLoading; | |||||
export default RoughScheduleSettingWrapper; | export default RoughScheduleSettingWrapper; |
@@ -0,0 +1,67 @@ | |||||
import React, {useEffect, useState} from 'react'; | |||||
import { FormControl, InputLabel, Select, MenuItem, Chip, Box } from '@mui/material'; | |||||
interface Option { | |||||
value: number; | |||||
label: string; | |||||
} | |||||
interface MultiSelectProps { | |||||
label: string; | |||||
options: Option[]; | |||||
selectedValues: number[]; | |||||
onChange: (values: number[]) => void; | |||||
} | |||||
const MultiSelect: React.FC<MultiSelectProps> = ({ label, options, selectedValues, onChange, isReset }) => { | |||||
const [displayValues, setDisplayValues] = useState<number[]>(selectedValues); | |||||
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { | |||||
const value = event.target.value as number[]; | |||||
console.log("[debug] value", value); | |||||
// Update display values state | |||||
setDisplayValues(value); | |||||
// Update selected values in parent component | |||||
onChange(value); | |||||
}; | |||||
useEffect(()=>{ | |||||
setDisplayValues([]); | |||||
}, [isReset]) | |||||
return ( | |||||
<FormControl fullWidth> | |||||
<InputLabel>{label}</InputLabel> | |||||
<Select | |||||
multiple | |||||
value={displayValues} | |||||
onChange={handleChange} | |||||
renderValue={(selected) => ( | |||||
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}> | |||||
{(selected as number[]).map((value) => ( | |||||
<Chip | |||||
key={value} | |||||
label={options.find(item => item.value == value).label} | |||||
sx={{ margin: 0.5 }} | |||||
/> | |||||
))} | |||||
</Box> | |||||
)} | |||||
> | |||||
{options.map((option) => ( | |||||
<MenuItem key={option.value} value={option.value}> | |||||
<input | |||||
type="checkbox" | |||||
checked={displayValues.indexOf(option.value) > -1} | |||||
readOnly | |||||
/> | |||||
{option.label} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
</FormControl> | |||||
); | |||||
}; | |||||
export default MultiSelect; |
@@ -21,12 +21,16 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | ||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||||
import { Box } from "@mui/material"; | import { Box } from "@mui/material"; | ||||
import MultiSelect from "@/components/SearchBox/MultiSelect"; | |||||
interface BaseCriterion<T extends string> { | interface BaseCriterion<T extends string> { | ||||
label: string; | label: string; | ||||
label2?: string; | label2?: string; | ||||
paramName: T; | paramName: T; | ||||
paramName2?: T; | paramName2?: T; | ||||
options?: T[]; | |||||
filterObj?: T; | |||||
handleSelectionChange: (selectedOptions: T[]) => void; | |||||
} | } | ||||
interface TextCriterion<T extends string> extends BaseCriterion<T> { | interface TextCriterion<T extends string> extends BaseCriterion<T> { | ||||
@@ -35,7 +39,14 @@ interface TextCriterion<T extends string> extends BaseCriterion<T> { | |||||
interface SelectCriterion<T extends string> extends BaseCriterion<T> { | interface SelectCriterion<T extends string> extends BaseCriterion<T> { | ||||
type: "select"; | type: "select"; | ||||
options: string[]; | |||||
options: T[]; | |||||
} | |||||
interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> { | |||||
type: "select"; | |||||
options: T[]; | |||||
selectedOptions: T[]; | |||||
handleSelectionChange: (selectedOptions: T[]) => void; | |||||
} | } | ||||
interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { | interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { | ||||
@@ -45,7 +56,8 @@ interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { | |||||
export type Criterion<T extends string> = | export type Criterion<T extends string> = | ||||
| TextCriterion<T> | | TextCriterion<T> | ||||
| SelectCriterion<T> | | SelectCriterion<T> | ||||
| DateRangeCriterion<T>; | |||||
| DateRangeCriterion<T> | |||||
| MultiSelectCriterion<T>; | |||||
interface Props<T extends string> { | interface Props<T extends string> { | ||||
criteria: Criterion<T>[]; | criteria: Criterion<T>[]; | ||||
@@ -70,6 +82,7 @@ function SearchBox<T extends string>({ | |||||
[criteria], | [criteria], | ||||
); | ); | ||||
const [inputs, setInputs] = useState(defaultInputs); | const [inputs, setInputs] = useState(defaultInputs); | ||||
const [isReset, setIsReset] = useState(false); | |||||
const makeInputChangeHandler = useCallback( | const makeInputChangeHandler = useCallback( | ||||
(paramName: T): React.ChangeEventHandler<HTMLInputElement> => { | (paramName: T): React.ChangeEventHandler<HTMLInputElement> => { | ||||
@@ -104,6 +117,7 @@ function SearchBox<T extends string>({ | |||||
const handleReset = () => { | const handleReset = () => { | ||||
setInputs(defaultInputs); | setInputs(defaultInputs); | ||||
onReset?.(); | onReset?.(); | ||||
setIsReset(!isReset); | |||||
}; | }; | ||||
const handleSearch = () => { | const handleSearch = () => { | ||||
@@ -126,6 +140,15 @@ function SearchBox<T extends string>({ | |||||
value={inputs[c.paramName]} | value={inputs[c.paramName]} | ||||
/> | /> | ||||
)} | )} | ||||
{c.type === "multi-select" && ( | |||||
<MultiSelect | |||||
label={c.label} | |||||
options={c?.options} | |||||
selectedValues={c.filterObj?.[c.paramName] ?? []} | |||||
onChange={c.handleSelectionChange} | |||||
isReset={isReset} | |||||
/> | |||||
)} | |||||
{c.type === "select" && ( | {c.type === "select" && ( | ||||
<FormControl fullWidth> | <FormControl fullWidth> | ||||
<InputLabel>{c.label}</InputLabel> | <InputLabel>{c.label}</InputLabel> | ||||