Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 

251 Zeilen
8.1 KiB

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