FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

538 行
16 KiB

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