Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

400 lignes
17 KiB

  1. // material-ui
  2. import {
  3. Button,
  4. Grid, TextField,
  5. Autocomplete,
  6. Typography
  7. } from '@mui/material';
  8. import MainCard from "components/MainCard";
  9. import { useForm } from "react-hook-form";
  10. import * as React from "react";
  11. import * as DateUtils from "utils/DateUtils";
  12. import * as ComboData from "utils/ComboData";
  13. import {PNSPS_BUTTON_THEME} from "../../../themes/buttonConst";
  14. import {ThemeProvider} from "@emotion/react";
  15. import {DatePicker} from "@mui/x-date-pickers/DatePicker";
  16. import dayjs from "dayjs";
  17. import {DemoItem} from "@mui/x-date-pickers/internals/demo";
  18. import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider";
  19. import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs";
  20. const getDefaultDateFrom = () => DateUtils.dateValue(new Date().setDate(new Date().getDate() - 14));
  21. const getDefaultDateTo = () => DateUtils.dateValue(new Date());
  22. const isSubmitDateEmpty = (value) =>
  23. value == null || value === "" || value === "dd / mm / yyyy";
  24. const isBlank = (value) => value == null || String(value).trim() === "";
  25. const toDayjsOrNull = (value) => {
  26. if (isSubmitDateEmpty(value)) return null;
  27. const d = dayjs(value);
  28. return d.isValid() ? d : null;
  29. };
  30. // ==============================|| DASHBOARD - DEFAULT ||============================== //
  31. const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => {
  32. const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom);
  33. const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo);
  34. const [status, setStatus] = React.useState(ComboData.paymentStatus[0]);
  35. const [payMethod, setPayMethod] = React.useState(ComboData.payMethod[0]);
  36. const marginBottom = 2.5;
  37. const [fromDateValue, setFromDateValue] = React.useState("dd / mm / yyyy");
  38. const [toDateValue, setToDateValue] = React.useState("dd / mm / yyyy");
  39. const prevHasOtherRef = React.useRef(null);
  40. React.useEffect(() => {
  41. if(searchCriteria.status!=undefined){
  42. if(searchCriteria.status === ""){
  43. ComboData.paymentStatus[0]
  44. }else{
  45. setStatus(ComboData.paymentStatus.find(item => item.type === searchCriteria.status))
  46. }
  47. }else{
  48. setStatus(ComboData.paymentStatus[0])
  49. }
  50. }, [searchCriteria]);
  51. React.useEffect(() => {
  52. const defaultPayMethod = ComboData.payMethod[0];
  53. const value = searchCriteria?.payMethod; // may be [], null, undefined, or array of strings
  54. if (!value || value.length === 0) {
  55. setPayMethod(defaultPayMethod);
  56. return;
  57. }
  58. // Find the matching entry whose type array matches value contents
  59. const found = ComboData.payMethod.find(item =>
  60. Array.isArray(item.type) &&
  61. item.type.length === value.length &&
  62. item.type.every((v, i) => v === value[i]) // strict positional match
  63. );
  64. setPayMethod(found ?? defaultPayMethod);
  65. }, [searchCriteria?.payMethod]);
  66. React.useEffect(() => {
  67. setFromDateValue(minDate);
  68. }, [minDate]);
  69. React.useEffect(() => {
  70. setToDateValue(maxDate);
  71. }, [maxDate]);
  72. const { reset, register, handleSubmit, watch } = useForm({
  73. defaultValues: {
  74. code: searchCriteria.code || "",
  75. transNo: searchCriteria.transNo || ""
  76. }
  77. });
  78. const code = watch("code");
  79. const transNo = watch("transNo");
  80. // add near the top inside the component (after useState for payMethod)
  81. const toPayMethodArray = (opt) => {
  82. if (!opt || opt.type === 'all') return [];
  83. return Array.isArray(opt.type) ? opt.type : [opt.type];
  84. };
  85. const clearSubmitDates = () => {
  86. setMinDate(null);
  87. setMaxDate(null);
  88. };
  89. const restoreDefaultSubmitDates = () => {
  90. setMinDate(getDefaultDateFrom());
  91. setMaxDate(getDefaultDateTo());
  92. };
  93. const hasOtherCriteria = (textFields = {}) => {
  94. if (!isBlank(textFields.code)) return true;
  95. if (!isBlank(textFields.transNo)) return true;
  96. if (status?.type && status.type !== "all" && status.type !== "") return true;
  97. if (payMethod?.type && payMethod.type !== "all") return true;
  98. return false;
  99. };
  100. React.useEffect(() => {
  101. const hasOther = hasOtherCriteria({ code, transNo });
  102. if (prevHasOtherRef.current === null) {
  103. prevHasOtherRef.current = hasOther;
  104. return;
  105. }
  106. if (hasOther && !prevHasOtherRef.current) {
  107. clearSubmitDates();
  108. } else if (!hasOther && prevHasOtherRef.current) {
  109. // Only refill defaults when From or To is empty; keep user-entered dates otherwise
  110. if (isSubmitDateEmpty(minDate) || isSubmitDateEmpty(maxDate)) {
  111. restoreDefaultSubmitDates();
  112. }
  113. }
  114. prevHasOtherRef.current = hasOther;
  115. }, [code, transNo, status, payMethod]);
  116. const onSubmit = (data) => {
  117. let sentDateFrom = "";
  118. let sentDateTo = "";
  119. const hasOther = hasOtherCriteria({
  120. code: data.code,
  121. transNo: data.transNo
  122. });
  123. const datesEmpty = isSubmitDateEmpty(fromDateValue) || isSubmitDateEmpty(toDateValue)
  124. || minDate == null || maxDate == null;
  125. if (!hasOther && datesEmpty) {
  126. const dateFrom = getDefaultDateFrom();
  127. const dateTo = getDefaultDateTo();
  128. setMinDate(dateFrom);
  129. setMaxDate(dateTo);
  130. sentDateFrom = dateFrom;
  131. sentDateTo = dateTo;
  132. } else if (!datesEmpty) {
  133. sentDateFrom = DateUtils.dateValue(fromDateValue);
  134. sentDateTo = DateUtils.dateValue(toDateValue);
  135. }
  136. const temp = {
  137. code: data.code,
  138. transNo: data.transNo,
  139. dateFrom: sentDateFrom,
  140. dateTo: sentDateTo,
  141. status : (status?.type && status?.type != 'all') ? status?.type : "",
  142. payMethod : toPayMethodArray(payMethod),
  143. start:0,
  144. limit:10
  145. };
  146. if (searchCriteria?.sort && searchCriteria?.direction) {
  147. temp.sort = searchCriteria.sort;
  148. temp.direction = searchCriteria.direction;
  149. }
  150. applySearch(temp);
  151. };
  152. function resetForm() {
  153. setStatus(ComboData.paymentStatus[0]);
  154. setPayMethod(ComboData.payMethod[0]);
  155. const dateFrom = getDefaultDateFrom();
  156. const dateTo = getDefaultDateTo();
  157. setMinDate(dateFrom);
  158. setMaxDate(dateTo);
  159. reset({
  160. code:"",
  161. transNo:""
  162. });
  163. prevHasOtherRef.current = false;
  164. localStorage.setItem('searchCriteria',"");
  165. applySearch({
  166. code: "",
  167. transNo: "",
  168. dateFrom,
  169. dateTo,
  170. status: "",
  171. payMethod: [],
  172. start: 0,
  173. limit: 10
  174. });
  175. }
  176. return (
  177. <MainCard xs={12} md={12} lg={12}
  178. border={false}
  179. content={false}
  180. >
  181. <form onSubmit={handleSubmit(onSubmit)} >
  182. <Grid container sx={{ backgroundColor: '#ffffff', ml: 2, mt: 1, mb: marginBottom}} width="98%">
  183. {/*row 1*/}
  184. <Grid item justifyContent="space-between" alignItems="center" sx={{mt:1,ml:3,mb:2.5}}>
  185. <Typography variant="pnspsFormHeader" >
  186. Search
  187. </Typography>
  188. </Grid>
  189. {/*row 2*/}
  190. <Grid container display="flex" alignItems={"center"}>
  191. <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3,mb: marginBottom}}>
  192. <TextField
  193. fullWidth
  194. {...register("code")}
  195. id='code'
  196. label="Application No."
  197. defaultValue={searchCriteria.code}
  198. InputLabelProps={{
  199. shrink: true
  200. }}
  201. />
  202. </Grid>
  203. <Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, mb:marginBottom}}>
  204. <Grid container spacing={1}>
  205. <Grid item xs={6}>
  206. <LocalizationProvider dateAdapter={AdapterDayjs}>
  207. <DemoItem components={['DatePicker']}>
  208. <DatePicker
  209. id="dateFrom"
  210. onError={() => {}}
  211. slotProps={{
  212. field: { readOnly: true, clearable: true },
  213. textField: {
  214. InputLabelProps: { shrink: true },
  215. error: false,
  216. helperText: null
  217. },
  218. }}
  219. format="DD/MM/YYYY"
  220. label="Payment Date (From)"
  221. value={toDayjsOrNull(minDate)}
  222. maxDate={toDayjsOrNull(maxDate)}
  223. onChange={(newValue) => {
  224. setMinDate(newValue && newValue.isValid?.() ? newValue : null);
  225. }}
  226. />
  227. </DemoItem >
  228. </LocalizationProvider>
  229. </Grid>
  230. <Grid item xs={6}>
  231. <LocalizationProvider dateAdapter={AdapterDayjs}>
  232. <DemoItem components={['DatePicker']}>
  233. <DatePicker
  234. id="dateTo"
  235. onError={() => {}}
  236. slotProps={{
  237. field: { readOnly: true, clearable: true },
  238. textField: {
  239. InputLabelProps: { shrink: true },
  240. error: false,
  241. helperText: null
  242. },
  243. }}
  244. format="DD/MM/YYYY"
  245. label="Payment Date (To)"
  246. value={toDayjsOrNull(maxDate)}
  247. minDate={toDayjsOrNull(minDate)}
  248. onChange={(newValue) => {
  249. setMaxDate(newValue && newValue.isValid?.() ? newValue : null);
  250. }}
  251. />
  252. </DemoItem >
  253. </LocalizationProvider>
  254. </Grid>
  255. </Grid>
  256. </Grid>
  257. <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: marginBottom}}>
  258. <TextField
  259. fullWidth
  260. {...register("transNo")}
  261. id='transNo'
  262. label="Payment No. / Payment Reference No."
  263. defaultValue={searchCriteria.transNo}
  264. InputLabelProps={{
  265. shrink: true
  266. }}
  267. />
  268. </Grid>
  269. <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: marginBottom }}>
  270. <Autocomplete
  271. {...register("status")}
  272. disablePortal={false}
  273. size="small"
  274. id="status"
  275. filterOptions={(options) => options}
  276. options={ComboData.paymentStatus}
  277. value={status}
  278. getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")}
  279. inputValue={status?.label ? status?.label : ""}
  280. onChange={(event, newValue) => {
  281. if(newValue==null){
  282. setStatus(ComboData.paymentStatus[0]);
  283. }else{
  284. setStatus(newValue);
  285. }
  286. }}
  287. sx={{
  288. '& .MuiInputBase-root': { alignItems: 'center' },
  289. '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' },
  290. '& .MuiOutlinedInput-root': { height: 40 }
  291. }}
  292. renderInput={(params) => (
  293. <TextField {...params}
  294. label="Status"
  295. InputLabelProps={{ shrink: true }}
  296. />
  297. )}
  298. />
  299. </Grid>
  300. <Grid item xs={9} s={6} md={5} lg={3} sx={{ ml: 3, mr: 3, mb: marginBottom }}>
  301. <Autocomplete
  302. {...register("payMethod")}
  303. disablePortal={false}
  304. size="small"
  305. id="payMethod"
  306. filterOptions={(options) => options}
  307. options={ComboData.payMethod}
  308. value={payMethod}
  309. getOptionLabel={(option) => (option?.label != null ? String(option.label) : "")}
  310. inputValue={payMethod?.label ? payMethod?.label : ""}
  311. onChange={(event, newValue) => {
  312. if(newValue==null){
  313. setPayMethod(ComboData.payMethod[0]);
  314. }else{
  315. setPayMethod(newValue);
  316. }
  317. }}
  318. sx={{
  319. '& .MuiInputBase-root': { alignItems: 'center' },
  320. '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' },
  321. '& .MuiOutlinedInput-root': { height: 40 }
  322. }}
  323. renderInput={(params) => (
  324. <TextField {...params}
  325. label="Payment Method"
  326. InputLabelProps={{ shrink: true }}
  327. />
  328. )}
  329. />
  330. </Grid>
  331. </Grid>
  332. </Grid>
  333. {/*last row*/}
  334. <Grid container maxWidth justifyContent="flex-end">
  335. <ThemeProvider theme={PNSPS_BUTTON_THEME}>
  336. <Grid item sx={{ ml: 3, mb: 3}}>
  337. <Button
  338. variant="contained"
  339. color="cancel"
  340. onClick={resetForm}
  341. >
  342. Reset
  343. </Button>
  344. </Grid>
  345. <Grid item sx={{ ml: 3, mr: 3, mb: 3 }}>
  346. <Button
  347. variant="contained"
  348. type="submit"
  349. disabled={onGridReady}
  350. >
  351. Submit
  352. </Button>
  353. </Grid>
  354. </ThemeProvider>
  355. </Grid>
  356. </form>
  357. </MainCard>
  358. );
  359. };
  360. export default SearchPublicNoticeForm;