25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

204 satır
6.4 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 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. interface BaseCriterion<T extends string> {
  24. label: string;
  25. label2?: string;
  26. paramName: T;
  27. paramName2?: T;
  28. }
  29. interface TextCriterion<T extends string> extends BaseCriterion<T> {
  30. type: "text";
  31. }
  32. interface SelectCriterion<T extends string> extends BaseCriterion<T> {
  33. type: "select";
  34. options: string[];
  35. }
  36. interface DateRangeCriterion<T extends string> extends BaseCriterion<T> {
  37. type: "dateRange";
  38. }
  39. export type Criterion<T extends string> =
  40. | TextCriterion<T>
  41. | SelectCriterion<T>
  42. | DateRangeCriterion<T>;
  43. interface Props<T extends string> {
  44. criteria: Criterion<T>[];
  45. onSearch: (inputs: Record<T, string>) => void;
  46. onReset?: () => void;
  47. }
  48. function SearchBox<T extends string>({
  49. criteria,
  50. onSearch,
  51. onReset,
  52. }: Props<T>) {
  53. const { t } = useTranslation("common");
  54. const defaultInputs = useMemo(
  55. () =>
  56. criteria.reduce<Record<T, string>>(
  57. (acc, c) => {
  58. return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" };
  59. },
  60. {} as Record<T, string>,
  61. ),
  62. [criteria],
  63. );
  64. const [inputs, setInputs] = useState(defaultInputs);
  65. const makeInputChangeHandler = useCallback(
  66. (paramName: T): React.ChangeEventHandler<HTMLInputElement> => {
  67. return (e) => {
  68. setInputs((i) => ({ ...i, [paramName]: e.target.value }));
  69. };
  70. },
  71. [],
  72. );
  73. const makeSelectChangeHandler = useCallback((paramName: T) => {
  74. return (e: SelectChangeEvent) => {
  75. setInputs((i) => ({ ...i, [paramName]: e.target.value }));
  76. };
  77. }, []);
  78. const makeDateChangeHandler = useCallback((paramName: T) => {
  79. return (e: any) => {
  80. setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
  81. };
  82. }, []);
  83. const makeDateToChangeHandler = useCallback((paramName: T) => {
  84. return (e: any) => {
  85. setInputs((i) => ({
  86. ...i,
  87. [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"),
  88. }));
  89. };
  90. }, []);
  91. const handleReset = () => {
  92. setInputs(defaultInputs);
  93. onReset?.();
  94. };
  95. const handleSearch = () => {
  96. onSearch(inputs);
  97. };
  98. return (
  99. <Card>
  100. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  101. <Typography variant="overline">{t("Search Criteria")}</Typography>
  102. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  103. {criteria.map((c) => {
  104. return (
  105. <Grid key={c.paramName} item xs={6}>
  106. {c.type === "text" && (
  107. <TextField
  108. label={c.label}
  109. fullWidth
  110. onChange={makeInputChangeHandler(c.paramName)}
  111. value={inputs[c.paramName]}
  112. />
  113. )}
  114. {c.type === "select" && (
  115. <FormControl fullWidth>
  116. <InputLabel>{c.label}</InputLabel>
  117. <Select
  118. label={c.label}
  119. onChange={makeSelectChangeHandler(c.paramName)}
  120. value={inputs[c.paramName]}
  121. >
  122. <MenuItem value={"All"}>{t("All")}</MenuItem>
  123. {c.options.map((option, index) => (
  124. <MenuItem key={`${option}-${index}`} value={option}>
  125. {option}
  126. </MenuItem>
  127. ))}
  128. </Select>
  129. </FormControl>
  130. )}
  131. {c.type === "dateRange" && (
  132. <LocalizationProvider
  133. dateAdapter={AdapterDayjs}
  134. // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
  135. adapterLocale="zh-hk"
  136. >
  137. <Box display="flex">
  138. <FormControl fullWidth>
  139. <DatePicker
  140. label={c.label}
  141. onChange={makeDateChangeHandler(c.paramName)}
  142. value={inputs[c.paramName] ? dayjs(inputs[c.paramName]) : null}
  143. />
  144. </FormControl>
  145. <Box
  146. display="flex"
  147. alignItems="center"
  148. justifyContent="center"
  149. marginInline={2}
  150. >
  151. {"-"}
  152. </Box>
  153. <FormControl fullWidth>
  154. <DatePicker
  155. label={c.label2}
  156. onChange={makeDateToChangeHandler(c.paramName)}
  157. value={inputs[c.paramName.concat("To") as T] ? dayjs(inputs[c.paramName.concat("To") as T]) : null}
  158. />
  159. </FormControl>
  160. </Box>
  161. </LocalizationProvider>
  162. )}
  163. </Grid>
  164. );
  165. })}
  166. </Grid>
  167. <CardActions sx={{ justifyContent: "flex-end" }}>
  168. <Button
  169. variant="text"
  170. startIcon={<RestartAlt />}
  171. onClick={handleReset}
  172. >
  173. {t("Reset")}
  174. </Button>
  175. <Button
  176. variant="outlined"
  177. startIcon={<Search />}
  178. onClick={handleSearch}
  179. >
  180. {t("Search")}
  181. </Button>
  182. </CardActions>
  183. </CardContent>
  184. </Card>
  185. );
  186. }
  187. export default SearchBox;