You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

600 line
22 KiB

  1. "use client";
  2. import {
  3. Card,
  4. CardHeader,
  5. CardContent,
  6. SxProps,
  7. Theme,
  8. Typography,
  9. Grid,
  10. TextField,
  11. FormControl,
  12. InputLabel,
  13. Select,
  14. MenuItem,
  15. Checkbox,
  16. FormControlLabel,
  17. Button,
  18. Chip,
  19. } from "@mui/material";
  20. import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
  21. import { darken, lighten, styled } from "@mui/material/styles";
  22. import { Controller, useForm, useFormContext } from "react-hook-form";
  23. import Stack from "@mui/material/Stack";
  24. import { useTranslation } from "react-i18next";
  25. import Box from "@mui/material/Box";
  26. import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
  27. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  28. import { DemoItem } from "@mui/x-date-pickers/internals/demo";
  29. import { DatePicker } from "@mui/x-date-pickers/DatePicker";
  30. import dayjs from "dayjs";
  31. import { useCallback, useEffect, useState } from "react";
  32. import { Check, Close, RestartAlt } from "@mui/icons-material";
  33. import { NumericFormat, NumericFormatProps } from "react-number-format";
  34. import * as React from "react";
  35. import CancelIcon from "@mui/icons-material/Cancel";
  36. interface Options {
  37. id: any;
  38. label: string;
  39. [key: string]: any;
  40. }
  41. export interface Field {
  42. // subtitle: string;
  43. id: string;
  44. label: string;
  45. type: string;
  46. value?: any;
  47. required?: boolean;
  48. pattern?: string;
  49. message?: string;
  50. options?: Options[] | null;
  51. readOnly?: boolean;
  52. size?: number;
  53. setValue?: any[];
  54. }
  55. interface CustomProps {
  56. onChange: (event: { target: { name: string; value: string } }) => void;
  57. name: string;
  58. }
  59. interface CustomInputFormProps {
  60. onSubmit: (data: any) => void;
  61. onSubmitError?: (data: any) => void;
  62. onCancel: () => void;
  63. // resetForm: () => void;
  64. Title?: string[];
  65. isActive: boolean;
  66. fieldLists: Field[][];
  67. }
  68. // interface SubComponents {
  69. // Loading: typeof CustomerSearchLoading;
  70. // }
  71. const CustomInputForm: React.FC<CustomInputFormProps> = ({
  72. Title,
  73. isActive,
  74. fieldLists,
  75. onSubmit,
  76. onSubmitError,
  77. onCancel,
  78. // resetForm,
  79. }) => {
  80. const { t } = useTranslation();
  81. const {
  82. reset,
  83. register,
  84. handleSubmit,
  85. control,
  86. formState: { errors },
  87. } = useForm();
  88. const [dateObj, setDateObj] = useState<any>(null);
  89. const [value, setValue] = useState<any>({});
  90. const [checkboxValue, setCheckboxValue] = useState({});
  91. interface DateObj {
  92. [key: string]: string;
  93. }
  94. const handleFormSubmit = (data: any) => {
  95. // if (date != null || date != undefined ) {
  96. // data.date = dayjs(date).format('YYYY-MM-DD');
  97. // }
  98. for (const key in data) {
  99. if (!isNaN(data[key])) {
  100. if (data[key] !== "") {
  101. if (Number.isInteger(parseFloat(data[key]))) {
  102. data[key] = parseInt(data[key]);
  103. } else {
  104. data[key] = parseFloat(data[key]);
  105. }
  106. }
  107. }
  108. }
  109. if (checkboxValue !== null) {
  110. data = { ...data, ...checkboxValue };
  111. }
  112. const finalData = {
  113. ...value,
  114. ...data,
  115. ...dateObj,
  116. };
  117. console.log(data);
  118. console.log(finalData);
  119. onSubmit(finalData);
  120. };
  121. const handleDateChange = (id: string, newValue: dayjs.Dayjs | null): void => {
  122. console.log(dayjs(newValue).format("YYYY-MM-DD"));
  123. setDateObj((prevValues: DateObj) => ({
  124. ...prevValues,
  125. [id]: newValue ? dayjs(newValue).format("YYYY-MM-DD") : "",
  126. }));
  127. };
  128. const handleCheckboxChange = (id: string, newValue: boolean): void => {
  129. setCheckboxValue((prevValues: { [key: string]: boolean }) => ({
  130. ...prevValues,
  131. [id]: newValue,
  132. }));
  133. };
  134. const handleAutocompleteChange = (id: any, newValue: any) => {
  135. setValue((prevValues: any) => ({
  136. ...prevValues,
  137. [id]: newValue,
  138. }));
  139. };
  140. const handleCancel = () => {
  141. reset();
  142. // resetForm();
  143. // setFromDate(null);
  144. setDateObj(null);
  145. setValue({});
  146. if (onCancel) {
  147. onCancel();
  148. }
  149. // if fields include setValue func
  150. // fieldLists.map((list) => {
  151. // list.map((field) => {
  152. // if (typeof field.setValue === 'function') {
  153. // field.setValue(typeof field.value === 'boolean' ? false : null);
  154. // } else if (typeof field.setValue === 'object') {
  155. // field.setValue.map((setFunc: any) => {
  156. // setFunc(null);
  157. // });
  158. // }
  159. // })
  160. // });
  161. // setToDate(null);
  162. };
  163. const handleReset = () => {
  164. reset();
  165. setDateObj(null);
  166. setValue({});
  167. };
  168. fieldLists.forEach((list) => {
  169. list.forEach((obj) => {
  170. if (
  171. obj.id === "created" ||
  172. obj.id === "createdBy" ||
  173. obj.id === "modified" ||
  174. obj.id === "deleted" ||
  175. obj.id === "modifiedBy"
  176. ) {
  177. obj.readOnly = true;
  178. }
  179. });
  180. });
  181. const NumericFormatCustom = React.forwardRef<NumericFormatProps, CustomProps>(
  182. function NumericFormatCustom(props, ref) {
  183. const { onChange, ...other } = props;
  184. return (
  185. <NumericFormat
  186. {...other}
  187. getInputRef={ref}
  188. onValueChange={(values) => {
  189. onChange({
  190. target: {
  191. name: props.name,
  192. value: values.value,
  193. },
  194. });
  195. }}
  196. thousandSeparator
  197. valueIsNumericString
  198. prefix="$"
  199. />
  200. );
  201. }
  202. );
  203. const [values, setValues] = React.useState({
  204. hourlyRate: "",
  205. });
  206. const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  207. setValues({
  208. ...values,
  209. [event.target.name]: event.target.value,
  210. });
  211. };
  212. return (
  213. <form onSubmit={handleSubmit(handleFormSubmit, onSubmitError)}>
  214. <Card sx={{ display: isActive ? "block" : "none" }}>
  215. <CardContent component={Stack} spacing={4}>
  216. <>
  217. {fieldLists.map((fieldList, fieldListIndex) => (
  218. <Box key={fieldListIndex}>
  219. {Title ? (
  220. <Typography
  221. variant="overline"
  222. display="block"
  223. marginBlockEnd={1}
  224. >
  225. {t(`${Title[fieldListIndex]}`)}
  226. </Typography>
  227. ) : null}
  228. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  229. {fieldList.map((field: Field) => {
  230. if (field.type === "text") {
  231. return (
  232. <Grid item xs={field.size ?? 6} key={field.id}>
  233. <TextField
  234. label={field.label}
  235. fullWidth
  236. {...register(field.id, {
  237. pattern: field.pattern
  238. ? new RegExp(field.pattern)
  239. : /.*/,
  240. })}
  241. defaultValue={field.value ? `${field.value}` : ""}
  242. inputProps={{
  243. readOnly: field.readOnly,
  244. }}
  245. required={field.required ?? false}
  246. error={Boolean(errors[field.id])}
  247. helperText={
  248. Boolean(errors[field.id]) && field.message
  249. }
  250. />
  251. </Grid>
  252. );
  253. } else if (field.type === "email") {
  254. return (
  255. <Grid item xs={field.size ?? 6} key={field.id}>
  256. <TextField
  257. label={field.label}
  258. fullWidth
  259. {...register(field.id, {
  260. pattern:
  261. /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/,
  262. })}
  263. defaultValue={!field.value ? `${field.value}` : ""}
  264. required={field.required ?? false}
  265. error={Boolean(errors[field.id])}
  266. helperText={
  267. Boolean(errors[field.id]) && field.message
  268. }
  269. />
  270. </Grid>
  271. );
  272. } else if (field.type === "multiDate") {
  273. // console.log(dayjs(field.value))
  274. return (
  275. <Grid item xs={field.size ?? 6} key={field.id}>
  276. <LocalizationProvider dateAdapter={AdapterDayjs}>
  277. <DemoItem>
  278. <DatePicker
  279. {...register(field.id)}
  280. key={field.id}
  281. label={field.label}
  282. defaultValue={
  283. field.value
  284. ? dayjs(field.value)
  285. : !dateObj
  286. ? null
  287. : !dateObj[field.id]
  288. ? null
  289. : dayjs(dateObj[field.id])
  290. } // Set initial value or use a default value from state
  291. onChange={(newValue) => {
  292. handleDateChange(field.id, newValue);
  293. }}
  294. slotProps={{
  295. textField: {
  296. required: field.required,
  297. },
  298. }}
  299. // error={Boolean(errors[field.id])}
  300. // slotProps={{
  301. // textField: {
  302. // helperText: Boolean(errors[field.id]) && field.message,
  303. // },
  304. // }}
  305. // required = {field.required ?? false},
  306. />
  307. </DemoItem>
  308. </LocalizationProvider>
  309. </Grid>
  310. );
  311. } else if (field.type === "combo-Obj") {
  312. return (
  313. <Grid item xs={field.size ?? 6} key={field.id}>
  314. <FormControl fullWidth>
  315. <InputLabel id={`${field.id}-label`}>
  316. {field.label}
  317. </InputLabel>
  318. <Controller
  319. name={field.id}
  320. control={control}
  321. defaultValue={
  322. field.value !== undefined ? field.value : ""
  323. }
  324. render={({ field: { onChange, value } }) => (
  325. <Select
  326. labelId={`${field.id}-label`}
  327. id={field.id}
  328. value={value}
  329. onChange={(event) => {
  330. onChange(event.target.value);
  331. const newValue = event.target.value;
  332. const selectedOption = field.options?.find(
  333. (option) => option.id === newValue
  334. );
  335. handleAutocompleteChange(
  336. field.id,
  337. selectedOption
  338. );
  339. }}
  340. required={field.required}
  341. >
  342. {field.options?.map((option) => (
  343. <MenuItem
  344. value={
  345. option.id !== undefined
  346. ? option.id
  347. : option
  348. }
  349. key={
  350. option.id !== undefined
  351. ? option.id
  352. : option
  353. }
  354. >
  355. {option.id ? option.label : ""}
  356. </MenuItem>
  357. ))}
  358. </Select>
  359. )}
  360. />
  361. </FormControl>
  362. </Grid>
  363. );
  364. } else if (field.type === "multiSelect-Obj") {
  365. return (
  366. <Grid item xs={field.size ?? 6} key={field.id}>
  367. <FormControl fullWidth>
  368. <InputLabel id={`${field.id}-label`}>{field.label}</InputLabel>
  369. <Controller
  370. name={field.id}
  371. control={control}
  372. defaultValue={
  373. field.value !== undefined ? field.value : []
  374. }
  375. render={({ field: { onChange, value } }) => (
  376. <Select
  377. labelId={`${field.id}-label`}
  378. id={field.id}
  379. value={value}
  380. multiple
  381. onChange={(event) => {
  382. onChange(event.target.value);
  383. const newValue = event.target.value;
  384. const selectedOption = field.options?.find(
  385. (option) => option.id === newValue
  386. );
  387. handleAutocompleteChange(
  388. field.id,
  389. selectedOption
  390. );
  391. }}
  392. renderValue={(selected) => {
  393. const selectedOption = field.options?.filter((option) => selected.includes(option.id));
  394. return (
  395. <Stack gap={1} direction="row" flexWrap="wrap">
  396. {selectedOption ? selectedOption.map(({id, label}) => {
  397. return (
  398. <Chip key={id} label={label} />
  399. )}) : null}
  400. </Stack>
  401. )}}
  402. required={field.required}
  403. >
  404. {field.options?.map((option) => (
  405. <MenuItem
  406. value={
  407. option.id !== undefined
  408. ? option.id
  409. : option
  410. }
  411. key={
  412. option.id !== undefined
  413. ? option.id
  414. : option
  415. }
  416. >
  417. {option.id ? option.label : ""}
  418. </MenuItem>
  419. ))}
  420. </Select>
  421. )}
  422. />
  423. </FormControl>
  424. </Grid>
  425. );
  426. } else if (field.type === "numeric") {
  427. return (
  428. <Grid item xs={field.size ?? 6} key={field.id}>
  429. <TextField
  430. fullWidth
  431. {...register(field.id)}
  432. id={field.id}
  433. label={field.label}
  434. defaultValue={!field.value ? `${field.value}` : ""}
  435. inputProps={{
  436. inputMode: "numeric",
  437. pattern: "^-?\\d*\\.?\\d+$",
  438. }}
  439. required={field.required}
  440. />
  441. </Grid>
  442. );
  443. } else if (field.type === "numeric-testing") {
  444. return (
  445. <Grid item xs={field.size ?? 6} key={field.id}>
  446. <FormControl fullWidth>
  447. <Controller
  448. {...register(field.id)}
  449. name={field.id}
  450. control={control}
  451. defaultValue={
  452. !field.value ? `${field.value}` : ""
  453. }
  454. render={({ field }) => {
  455. console.log(field);
  456. return (
  457. <NumericFormat
  458. {...field}
  459. customInput={TextField}
  460. thousandSeparator
  461. valueIsNumericString
  462. prefix="$"
  463. label={t(field.name)}
  464. />
  465. );
  466. }}
  467. />
  468. </FormControl>
  469. </Grid>
  470. );
  471. } else if (field.type === "numeric-positive") {
  472. return (
  473. <Grid item xs={field.size ?? 6} key={field.id}>
  474. <TextField
  475. fullWidth
  476. {...register(field.id)}
  477. id={field.id}
  478. name={field.id}
  479. label={field.label}
  480. defaultValue={!field.value ? `${field.value}` : ""}
  481. inputProps={{
  482. inputMode: "numeric",
  483. pattern: "[0-9]*[.]?[0-9]*",
  484. }}
  485. required={field.required}
  486. />
  487. </Grid>
  488. );
  489. } else if (field.type === "checkbox") {
  490. return (
  491. <Grid item xs={field.size ?? 6} key={field.id}>
  492. <FormControlLabel
  493. control={
  494. <Checkbox
  495. defaultChecked={field.value}
  496. onChange={(event) => {
  497. handleCheckboxChange(
  498. field.id,
  499. event.target.checked
  500. );
  501. }}
  502. color="primary"
  503. />
  504. }
  505. label={
  506. <Typography variant="h4">
  507. {field.label}
  508. </Typography>
  509. }
  510. />
  511. </Grid>
  512. );
  513. } else if (field.type === "remarks") {
  514. return (
  515. <Grid item xs={12} key={field.id}>
  516. <TextField
  517. fullWidth
  518. multiline
  519. rows={4}
  520. variant="filled"
  521. {...register(field.id)}
  522. defaultValue={field.value ? `${field.value}` : ""}
  523. id={field.id}
  524. label={field.label}
  525. required={field.required}
  526. />
  527. </Grid>
  528. );
  529. } else {
  530. return (
  531. <Grid item xs={field.size ?? 6} key={field.id}>
  532. <TextField
  533. fullWidth
  534. {...register(
  535. field.id
  536. // , { required: true }
  537. )}
  538. id={field.id}
  539. label={field.label}
  540. defaultValue={`${field.value}`}
  541. required={field.required}
  542. inputProps={{
  543. readOnly: field.readOnly,
  544. }}
  545. />
  546. </Grid>
  547. );
  548. }
  549. })}
  550. </Grid>
  551. </Box>
  552. ))}
  553. <Stack direction="row" justifyContent="flex-end" gap={1}>
  554. <Button
  555. variant="text"
  556. startIcon={<RestartAlt />}
  557. onClick={handleReset}
  558. >
  559. {t("Reset")}
  560. </Button>
  561. <Button
  562. variant="outlined"
  563. startIcon={<Close />}
  564. onClick={handleCancel}
  565. >
  566. {t("Cancel")}
  567. </Button>
  568. <Button variant="contained" startIcon={<Check />} type="submit">
  569. {t("Confirm")}
  570. </Button>
  571. </Stack>
  572. </>
  573. </CardContent>
  574. </Card>
  575. </form>
  576. );
  577. };
  578. // CustomInputForm.Loading = CustomerSearchLoading;
  579. export default CustomInputForm;