FPSMS-frontend
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

568 rader
16 KiB

  1. "use client";
  2. import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions";
  3. import {
  4. Autocomplete,
  5. Box,
  6. Button,
  7. Card,
  8. CardContent,
  9. FormControl,
  10. Grid,
  11. Modal,
  12. ModalProps,
  13. Stack,
  14. TextField,
  15. Tooltip,
  16. Typography,
  17. } from "@mui/material";
  18. import { Controller, useFormContext } from "react-hook-form";
  19. import { useTranslation } from "react-i18next";
  20. import StyledDataGrid from "../StyledDataGrid";
  21. import { useCallback, useEffect, useMemo, useState } from "react";
  22. import {
  23. GridColDef,
  24. GridRowIdGetter,
  25. GridRowModel,
  26. useGridApiContext,
  27. GridRenderCellParams,
  28. GridRenderEditCellParams,
  29. useGridApiRef,
  30. } from "@mui/x-data-grid";
  31. import InputDataGrid from "../InputDataGrid";
  32. import { TableRow } from "../InputDataGrid/InputDataGrid";
  33. import TwoLineCell from "./TwoLineCell";
  34. import QcSelect from "./QcSelect";
  35. import { QcItemWithChecks } from "@/app/api/qc";
  36. import { GridEditInputCell } from "@mui/x-data-grid";
  37. import { StockInLine } from "@/app/api/po";
  38. import { WarehouseResult } from "@/app/api/warehouse";
  39. import {
  40. OUTPUT_DATE_FORMAT,
  41. stockInLineStatusMap,
  42. } from "@/app/utils/formatUtil";
  43. import { QRCodeSVG } from "qrcode.react";
  44. import { QrCode } from "../QrCode";
  45. import ReactQrCodeScanner, {
  46. ScannerConfig,
  47. } from "../ReactQrCodeScanner/ReactQrCodeScanner";
  48. import { QrCodeInfo } from "@/app/api/qrcode";
  49. import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
  50. import dayjs from "dayjs";
  51. import arraySupport from "dayjs/plugin/arraySupport";
  52. import { dummyPutAwayLine } from "./dummyQcTemplate";
  53. import { PrinterCombo } from "@/app/api/settings/printer";
  54. dayjs.extend(arraySupport);
  55. interface Props {
  56. itemDetail: StockInLine;
  57. warehouse: WarehouseResult[];
  58. disabled: boolean;
  59. // qc: QcItemWithChecks[];
  60. printerCombo: PrinterCombo[];
  61. }
  62. type EntryError =
  63. | {
  64. [field in keyof PutAwayLine]?: string;
  65. }
  66. | undefined;
  67. type PutAwayRow = TableRow<Partial<PutAwayLine>, EntryError>;
  68. const style = {
  69. position: "absolute",
  70. top: "50%",
  71. left: "50%",
  72. transform: "translate(-50%, -50%)",
  73. bgcolor: "background.paper",
  74. pt: 5,
  75. px: 5,
  76. pb: 10,
  77. width: "auto",
  78. };
  79. const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, printerCombo }) => {
  80. const { t } = useTranslation("purchaseOrder");
  81. const apiRef = useGridApiRef();
  82. const {
  83. register,
  84. formState: { errors, defaultValues, touchedFields },
  85. watch,
  86. control,
  87. setValue,
  88. getValues,
  89. reset,
  90. resetField,
  91. setError,
  92. clearErrors,
  93. } = useFormContext<PutAwayInput>();
  94. console.log(itemDetail);
  95. // const [recordQty, setRecordQty] = useState(0);
  96. const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId);
  97. const filteredWarehouse = useMemo(() => {
  98. // do filtering here if any
  99. return warehouse;
  100. }, []);
  101. const defaultOption = {
  102. value: 0, // think think sin
  103. label: t("Select warehouse"),
  104. group: "default",
  105. };
  106. const options = useMemo(() => {
  107. return [
  108. // {
  109. // value: 0, // think think sin
  110. // label: t("Select warehouse"),
  111. // group: "default",
  112. // },
  113. ...filteredWarehouse.map((w) => ({
  114. value: w.id,
  115. label: `${w.code} - ${w.name}`,
  116. group: "existing",
  117. })),
  118. ];
  119. }, [filteredWarehouse]);
  120. const currentValue =
  121. warehouseId > 0
  122. ? options.find((o) => o.value === warehouseId)
  123. : options.find((o) => o.value === getValues("warehouseId")) ||
  124. defaultOption;
  125. const onChange = useCallback(
  126. (
  127. event: React.SyntheticEvent,
  128. newValue: { value: number; group: string } | { value: number }[],
  129. ) => {
  130. const singleNewVal = newValue as {
  131. value: number;
  132. group: string;
  133. };
  134. console.log(singleNewVal);
  135. console.log("onChange");
  136. // setValue("warehouseId", singleNewVal.value);
  137. setWarehouseId(singleNewVal.value);
  138. },
  139. [],
  140. );
  141. console.log(watch("putAwayLines"))
  142. // const accQty = watch("acceptedQty");
  143. // const validateForm = useCallback(() => {
  144. // console.log(accQty);
  145. // if (accQty > itemDetail.acceptedQty) {
  146. // setError("acceptedQty", {
  147. // message: `acceptedQty must not greater than ${itemDetail.acceptedQty}`,
  148. // type: "required",
  149. // });
  150. // }
  151. // if (accQty < 1) {
  152. // setError("acceptedQty", {
  153. // message: `minimal value is 1`,
  154. // type: "required",
  155. // });
  156. // }
  157. // if (isNaN(accQty)) {
  158. // setError("acceptedQty", {
  159. // message: `value must be a number`,
  160. // type: "required",
  161. // });
  162. // }
  163. // }, [accQty]);
  164. // useEffect(() => {
  165. // clearErrors();
  166. // validateForm();
  167. // }, [validateForm]);
  168. const qrContent = useMemo(
  169. () => ({
  170. stockInLineId: itemDetail.id,
  171. itemId: itemDetail.itemId,
  172. lotNo: itemDetail.lotNo,
  173. // warehouseId: 2 // for testing
  174. // expiryDate: itemDetail.expiryDate,
  175. // productionDate: itemDetail.productionDate,
  176. // supplier: itemDetail.supplier,
  177. // poCode: itemDetail.poCode,
  178. }),
  179. [itemDetail],
  180. );
  181. const [isOpenScanner, setOpenScanner] = useState(false);
  182. const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
  183. (...args) => {
  184. setOpenScanner(false);
  185. },
  186. [],
  187. );
  188. const onOpenScanner = useCallback(() => {
  189. setOpenScanner(true);
  190. }, []);
  191. const onCloseScanner = useCallback(() => {
  192. setOpenScanner(false);
  193. }, []);
  194. const scannerConfig = useMemo<ScannerConfig>(
  195. () => ({
  196. onUpdate: (err, result) => {
  197. console.log(result);
  198. console.log(Boolean(result));
  199. if (result) {
  200. const data: QrCodeInfo = JSON.parse(result.getText());
  201. console.log(data);
  202. if (data.warehouseId) {
  203. console.log(data.warehouseId);
  204. setWarehouseId(data.warehouseId);
  205. onCloseScanner();
  206. }
  207. } else return;
  208. },
  209. }),
  210. [onCloseScanner],
  211. );
  212. // QR Code Scanner
  213. const scanner = useQrCodeScannerContext();
  214. useEffect(() => {
  215. if (isOpenScanner) {
  216. scanner.startScan();
  217. } else if (!isOpenScanner) {
  218. scanner.stopScan();
  219. }
  220. }, [isOpenScanner]);
  221. useEffect(() => {
  222. if (scanner.values.length > 0) {
  223. console.log(scanner.values[0]);
  224. const data: QrCodeInfo = JSON.parse(scanner.values[0]);
  225. console.log(data);
  226. if (data.warehouseId) {
  227. console.log(data.warehouseId);
  228. setWarehouseId(data.warehouseId);
  229. onCloseScanner();
  230. }
  231. scanner.resetScan();
  232. }
  233. }, [scanner.values]);
  234. useEffect(() => {
  235. setValue("status", "received");
  236. // setValue("status", "completed");
  237. setValue("warehouseId", options[0].value); //TODO: save all warehouse entry?
  238. }, []);
  239. useEffect(() => {
  240. if (warehouseId > 0) {
  241. setValue("warehouseId", warehouseId);
  242. clearErrors("warehouseId");
  243. }
  244. }, [warehouseId]);
  245. const getWarningTextHardcode = useCallback((): string | undefined => {
  246. console.log(options)
  247. if (options.length === 0) return undefined
  248. const defaultWarehouseId = options[0].value;
  249. const currWarehouseId = watch("warehouseId");
  250. if (defaultWarehouseId !== currWarehouseId) {
  251. return t("not default warehosue");
  252. }
  253. return undefined;
  254. }, [options]);
  255. const columns = useMemo<GridColDef[]>(
  256. () => [
  257. {
  258. field: "qty",
  259. headerName: t("qty"),
  260. flex: 1,
  261. editable: true,
  262. // renderCell(params) {
  263. // return <>100</>
  264. // },
  265. },
  266. {
  267. field: "warehouse",
  268. headerName: t("warehouse"),
  269. flex: 1,
  270. editable: true,
  271. renderEditCell: (params) => {
  272. const index = params.api.getRowIndexRelativeToVisibleRows(params.row.id)
  273. // console.log(index)
  274. // console.log(watch(`putAwayLines.${index}.warehouse`))
  275. return <Autocomplete
  276. fullWidth
  277. disableClearable
  278. options={options}
  279. // defaultValue={options.find((o) => o.value === itemDetail.defaultWarehouseId)}
  280. // value={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))}
  281. defaultValue={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))}
  282. onChange={(event, value) => {
  283. params.api.setEditCellValue({ id: params.id, field: params.field, value: options.find((o) => o.value === value.value)?.label ?? ""})
  284. params.api.setEditCellValue({ id: params.id, field: "warehouseId", value: value.value})
  285. // setValue(`putAwayLines.${index}.warehouseId`, value.value)
  286. // setValue(`putAwayLines.${index}.warehouse`, options.find((o) => o.value === value.value)?.label ?? "")
  287. }}
  288. renderInput={(params) => (
  289. <TextField
  290. {...params}
  291. variant="outlined"
  292. // label={t("Warehouse")}
  293. />
  294. )}
  295. />
  296. }
  297. // renderCell(params) {
  298. // return <>{filteredWarehouse[0].name}</>
  299. // },
  300. },
  301. {
  302. field: "printQty",
  303. headerName: t("printQty"),
  304. flex: 1,
  305. editable: true,
  306. // renderCell(params) {
  307. // return <>100</>
  308. // },
  309. },
  310. ], [])
  311. const validation = useCallback(
  312. (newRow: GridRowModel<PutAwayRow>): EntryError => {
  313. const error: EntryError = {};
  314. const { qty, warehouseId, printQty } = newRow;
  315. return Object.keys(error).length > 0 ? error : undefined;
  316. },
  317. [],
  318. );
  319. const addRowDefaultValue = useMemo(() => {
  320. const defaultMaxQty = watch("acceptedQty") - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0)
  321. const defaultWarehouseId = itemDetail.defaultWarehouseId ?? 1
  322. const defaultWarehouse = options.find((o) => o.value === defaultWarehouseId)?.label
  323. return {qty: defaultMaxQty, warehouseId: defaultWarehouseId, warehouse: defaultWarehouse, printQty: 1 } as Partial<PutAwayLine>
  324. }, [])
  325. return (
  326. <Grid container justifyContent="flex-start" alignItems="flex-start">
  327. <Grid item xs={12}>
  328. <Typography variant="h6" display="block" marginBlockEnd={1}>
  329. {t("Putaway Detail")}
  330. </Typography>
  331. </Grid>
  332. <Grid
  333. container
  334. justifyContent="flex-start"
  335. alignItems="flex-start"
  336. spacing={2}
  337. sx={{ mt: 0.5 }}
  338. >
  339. <Grid item xs={12}>
  340. <TextField
  341. label={t("LotNo")}
  342. fullWidth
  343. value={itemDetail.lotNo}
  344. disabled
  345. />
  346. </Grid>
  347. <Grid item xs={6}>
  348. <TextField
  349. label={t("Supplier")}
  350. fullWidth
  351. value={itemDetail.supplier}
  352. disabled
  353. />
  354. </Grid>
  355. <Grid item xs={6}>
  356. <TextField
  357. label={t("Po Code")}
  358. fullWidth
  359. value={itemDetail.poCode}
  360. disabled
  361. />
  362. </Grid>
  363. <Grid item xs={6}>
  364. <TextField
  365. label={t("itemName")}
  366. fullWidth
  367. value={itemDetail.itemName}
  368. disabled
  369. />
  370. </Grid>
  371. <Grid item xs={6}>
  372. <TextField
  373. label={t("itemNo")}
  374. fullWidth
  375. value={itemDetail.itemNo}
  376. disabled
  377. />
  378. </Grid>
  379. <Grid item xs={6}>
  380. <TextField
  381. label={t("qty")}
  382. fullWidth
  383. value={itemDetail.acceptedQty}
  384. disabled
  385. />
  386. </Grid>
  387. <Grid item xs={6}>
  388. <TextField
  389. label={t("productionDate")}
  390. fullWidth
  391. value={
  392. // dayjs(itemDetail.productionDate)
  393. dayjs()
  394. // .add(-1, "month")
  395. .format(OUTPUT_DATE_FORMAT)}
  396. disabled
  397. />
  398. </Grid>
  399. <Grid item xs={6}>
  400. <TextField
  401. label={t("expiryDate")}
  402. fullWidth
  403. value={
  404. // dayjs(itemDetail.expiryDate)
  405. dayjs()
  406. .add(20, "day")
  407. .format(OUTPUT_DATE_FORMAT)}
  408. disabled
  409. />
  410. </Grid>
  411. <Grid item xs={6}>
  412. <FormControl fullWidth>
  413. <Autocomplete
  414. noOptionsText={t("No Warehouse")}
  415. disableClearable
  416. disabled
  417. fullWidth
  418. defaultValue={options[0]} /// modify this later
  419. // onChange={onChange}
  420. getOptionLabel={(option) => option.label}
  421. options={options}
  422. renderInput={(params) => (
  423. <TextField {...params} label={t("Default Warehouse")} />
  424. )}
  425. />
  426. </FormControl>
  427. </Grid>
  428. {/* <Grid item xs={5.5}>
  429. <TextField
  430. label={t("acceptedQty")}
  431. fullWidth
  432. {...register("acceptedQty", {
  433. required: "acceptedQty required!",
  434. min: 1,
  435. max: itemDetail.acceptedQty,
  436. valueAsNumber: true,
  437. })}
  438. // defaultValue={itemDetail.acceptedQty}
  439. disabled={disabled}
  440. error={Boolean(errors.acceptedQty)}
  441. helperText={errors.acceptedQty?.message}
  442. />
  443. </Grid>
  444. <Grid item xs={1}>
  445. <Button disabled={disabled} onClick={onOpenScanner}>
  446. {t("bind")}
  447. </Button>
  448. </Grid> */}
  449. {/* <Grid item xs={5.5}>
  450. <Controller
  451. control={control}
  452. name="warehouseId"
  453. render={({ field }) => {
  454. console.log(field);
  455. return (
  456. <Autocomplete
  457. noOptionsText={t("No Warehouse")}
  458. disableClearable
  459. fullWidth
  460. value={options.find((o) => o.value == field.value)}
  461. onChange={onChange}
  462. getOptionLabel={(option) => option.label}
  463. options={options}
  464. renderInput={(params) => (
  465. <TextField
  466. {...params}
  467. label={"Select warehouse"}
  468. error={Boolean(errors.warehouseId?.message)}
  469. helperText={warehouseHelperText}
  470. // helperText={errors.warehouseId?.message}
  471. />
  472. )}
  473. />
  474. );
  475. }}
  476. />
  477. <FormControl fullWidth>
  478. <Autocomplete
  479. noOptionsText={t("No Warehouse")}
  480. disableClearable
  481. fullWidth
  482. // value={warehouseId > 0
  483. // ? options.find((o) => o.value === warehouseId)
  484. // : undefined}
  485. defaultValue={options[0]}
  486. // defaultValue={options.find((o) => o.value === 1)}
  487. value={currentValue}
  488. onChange={onChange}
  489. getOptionLabel={(option) => option.label}
  490. options={options}
  491. renderInput={(params) => (
  492. <TextField
  493. {...params}
  494. // label={"Select warehouse"}
  495. disabled={disabled}
  496. error={Boolean(errors.warehouseId?.message)}
  497. helperText={
  498. errors.warehouseId?.message ?? getWarningTextHardcode()
  499. }
  500. // helperText={warehouseHelperText}
  501. />
  502. )}
  503. />
  504. </FormControl>
  505. </Grid> */}
  506. <Grid
  507. item
  508. xs={12}
  509. style={{ display: "flex", justifyContent: "center" }}
  510. >
  511. {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */}
  512. <InputDataGrid<PutAwayInput, PutAwayLine, EntryError>
  513. apiRef={apiRef}
  514. checkboxSelection={false}
  515. _formKey={"putAwayLines"}
  516. columns={columns}
  517. validateRow={validation}
  518. needAdd={true}
  519. showRemoveBtn={false}
  520. addRowDefaultValue={addRowDefaultValue}
  521. />
  522. </Grid>
  523. </Grid>
  524. {/* <Grid
  525. container
  526. justifyContent="flex-start"
  527. alignItems="flex-start"
  528. spacing={2}
  529. sx={{ mt: 0.5 }}
  530. >
  531. <Button onClick={onOpenScanner}>bind</Button>
  532. </Grid> */}
  533. <Modal open={isOpenScanner} onClose={closeHandler}>
  534. <Box sx={style}>
  535. <Typography variant="h4">
  536. {t("Please scan warehouse qr code.")}
  537. </Typography>
  538. {/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */}
  539. </Box>
  540. </Modal>
  541. </Grid>
  542. );
  543. };
  544. export default PutAwayForm;