FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 

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