FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

SearchBox.tsx 7.7 KiB

10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
10 mesi fa
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. "use client";
  2. import Grid from "@mui/material/Grid";
  3. import Card from "@mui/material/Card";
  4. import CardContent from "@mui/material/CardContent";
  5. import Typography from "@mui/material/Typography";
  6. import React, { useCallback, useMemo, useState } from "react";
  7. import { useTranslation } from "react-i18next";
  8. import TextField from "@mui/material/TextField";
  9. import FormControl from "@mui/material/FormControl";
  10. import InputLabel from "@mui/material/InputLabel";
  11. import Select, { SelectChangeEvent } from "@mui/material/Select";
  12. import MenuItem from "@mui/material/MenuItem";
  13. import CardActions from "@mui/material/CardActions";
  14. import Button from "@mui/material/Button";
  15. import RestartAlt from "@mui/icons-material/RestartAlt";
  16. import Search from "@mui/icons-material/Search";
  17. import dayjs from "dayjs";
  18. import "dayjs/locale/zh-hk";
  19. import { DatePicker } from "@mui/x-date-pickers/DatePicker";
  20. import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
  21. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  22. import { Box } from "@mui/material";
  23. import MultiSelect from "@/components/SearchBox/MultiSelect";
  24. interface BaseCriterion<T extends string> {
  25. label: string;
  26. label2?: string;
  27. paramName: T;
  28. paramName2?: T;
  29. options?: T[] | string[];
  30. filterObj?: T;
  31. handleSelectionChange?: (selectedOptions: T[]) => void;
  32. }
  33. interface TextCriterion<T extends string> extends BaseCriterion<T> {
  34. type: "text";
  35. }
  36. interface SelectCriterion<T extends string> extends BaseCriterion<T> {
  37. type: "select";
  38. options: string[];
  39. }
  40. interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> {
  41. type: "multi-select";
  42. options: T[];
  43. selectedOptions: T[];
  44. handleSelectionChange: (selectedOptions: T[]) => void;
  45. }
  46. interface DateRangeCriterion<T extends string> extends BaseCriterion<T> {
  47. type: "dateRange";
  48. }
  49. export type Criterion<T extends string> =
  50. | TextCriterion<T>
  51. | SelectCriterion<T>
  52. | DateRangeCriterion<T>
  53. | MultiSelectCriterion<T>;
  54. interface Props<T extends string> {
  55. criteria: Criterion<T>[];
  56. onSearch: (inputs: Record<T, string>) => void;
  57. onReset?: () => void;
  58. }
  59. function SearchBox<T extends string>({
  60. criteria,
  61. onSearch,
  62. onReset,
  63. }: Props<T>) {
  64. const { t } = useTranslation("common");
  65. const defaultInputs = useMemo(
  66. () =>
  67. criteria.reduce<Record<T, string>>(
  68. (acc, c) => {
  69. return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" };
  70. },
  71. {} as Record<T, string>,
  72. ),
  73. [criteria],
  74. );
  75. const [inputs, setInputs] = useState(defaultInputs);
  76. const [isReset, setIsReset] = useState(false);
  77. const makeInputChangeHandler = useCallback(
  78. (paramName: T): React.ChangeEventHandler<HTMLInputElement> => {
  79. return (e) => {
  80. setInputs((i) => ({ ...i, [paramName]: e.target.value }));
  81. };
  82. },
  83. [],
  84. );
  85. const makeSelectChangeHandler = useCallback((paramName: T) => {
  86. return (e: SelectChangeEvent) => {
  87. setInputs((i) => ({ ...i, [paramName]: e.target.value }));
  88. };
  89. }, []);
  90. const makeDateChangeHandler = useCallback((paramName: T) => {
  91. return (e: any) => {
  92. setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
  93. };
  94. }, []);
  95. const makeDateToChangeHandler = useCallback((paramName: T) => {
  96. return (e: any) => {
  97. setInputs((i) => ({
  98. ...i,
  99. [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"),
  100. }));
  101. };
  102. }, []);
  103. const handleReset = () => {
  104. setInputs(defaultInputs);
  105. onReset?.();
  106. setIsReset(!isReset);
  107. };
  108. const handleSearch = () => {
  109. onSearch(inputs);
  110. };
  111. return (
  112. <Card>
  113. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  114. <Typography variant="overline">{t("Search Criteria")}</Typography>
  115. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  116. {criteria.map((c) => {
  117. return (
  118. <Grid key={c.paramName} item xs={6}>
  119. {c.type === "text" && (
  120. <TextField
  121. label={c.label}
  122. fullWidth
  123. onChange={makeInputChangeHandler(c.paramName)}
  124. value={inputs[c.paramName]}
  125. />
  126. )}
  127. {c.type === "multi-select" && (
  128. <MultiSelect
  129. label={c.label}
  130. options={c?.options}
  131. selectedValues={c.filterObj?.[c.paramName] ?? []}
  132. onChange={c.handleSelectionChange}
  133. isReset={isReset}
  134. />
  135. )}
  136. {c.type === "select" && (
  137. <FormControl fullWidth>
  138. <InputLabel>{c.label}</InputLabel>
  139. <Select
  140. label={c.label}
  141. onChange={makeSelectChangeHandler(c.paramName)}
  142. value={inputs[c.paramName]}
  143. >
  144. <MenuItem value={"All"}>{t("All")}</MenuItem>
  145. {c.options.map((option) => (
  146. <MenuItem key={option} value={option}>
  147. {option}
  148. </MenuItem>
  149. ))}
  150. </Select>
  151. </FormControl>
  152. )}
  153. {c.type === "dateRange" && (
  154. <LocalizationProvider
  155. dateAdapter={AdapterDayjs}
  156. // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
  157. adapterLocale="zh-hk"
  158. >
  159. <Box display="flex">
  160. <FormControl fullWidth>
  161. <DatePicker
  162. label={c.label}
  163. onChange={makeDateChangeHandler(c.paramName)}
  164. />
  165. </FormControl>
  166. <Box
  167. display="flex"
  168. alignItems="center"
  169. justifyContent="center"
  170. marginInline={2}
  171. >
  172. {"-"}
  173. </Box>
  174. <FormControl fullWidth>
  175. <DatePicker
  176. label={c.label2}
  177. onChange={makeDateToChangeHandler(c.paramName)}
  178. />
  179. </FormControl>
  180. </Box>
  181. </LocalizationProvider>
  182. )}
  183. {c.type === "date" && (
  184. <LocalizationProvider
  185. dateAdapter={AdapterDayjs}
  186. // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
  187. adapterLocale="zh-hk"
  188. >
  189. <Box display="flex">
  190. <FormControl fullWidth>
  191. <DatePicker
  192. label={c.label}
  193. onChange={makeDateChangeHandler(c.paramName)}
  194. />
  195. </FormControl>
  196. </Box>
  197. </LocalizationProvider>
  198. )}
  199. </Grid>
  200. );
  201. })}
  202. </Grid>
  203. <CardActions sx={{ justifyContent: "flex-end" }}>
  204. <Button
  205. variant="text"
  206. startIcon={<RestartAlt />}
  207. onClick={handleReset}
  208. >
  209. {t("Reset")}
  210. </Button>
  211. <Button
  212. variant="outlined"
  213. startIcon={<Search />}
  214. onClick={handleSearch}
  215. >
  216. {t("Search")}
  217. </Button>
  218. </CardActions>
  219. </CardContent>
  220. </Card>
  221. );
  222. }
  223. export default SearchBox;