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

PutAwayModal.tsx 29 KiB

4日前
5ヶ月前
4日前
5ヶ月前
4日前
2ヶ月前
2ヶ月前
4日前
4日前
5ヶ月前
4日前
5ヶ月前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
4日前
5ヶ月前
5ヶ月前
4日前
4日前
4日前
5ヶ月前
5ヶ月前
4日前
5ヶ月前
5ヶ月前
4日前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Grid,
  6. Modal,
  7. ModalProps,
  8. Stack,
  9. TextField,
  10. Typography,
  11. Paper,
  12. Divider,
  13. } from "@mui/material";
  14. import { fetchItemForPutAway } from "@/app/api/stockIn/actions";
  15. import { Result } from "@/app/api/settings/item"; // 只导入类型
  16. import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
  17. import ReactQrCodeScanner, {
  18. ScannerConfig,
  19. } from "../ReactQrCodeScanner/ReactQrCodeScanner";
  20. import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
  21. import {
  22. fetchStockInLineInfo,
  23. StockInLineEntry,
  24. updateStockInLine,
  25. } from "@/app/api/stockIn/actions";
  26. import { ModalFormInput, StockInLine } from "@/app/api/stockIn";
  27. import { WarehouseResult } from "@/app/api/warehouse";
  28. // import { QrCodeInfo } from "@/app/api/qrcde";
  29. import { Check, QrCode, ErrorOutline, CheckCircle } from "@mui/icons-material";
  30. import { useTranslation } from "react-i18next";
  31. import { useSearchParams } from "next/navigation";
  32. import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
  33. import LoadingComponent from "../General/LoadingComponent";
  34. import StockInForm from "../StockIn/StockInForm";
  35. import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  36. import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider";
  37. import { msg } from "../Swal/CustomAlerts";
  38. import { PutAwayRecord } from ".";
  39. import FgStockInForm from "../StockIn/FgStockInForm";
  40. import Swal from "sweetalert2";
  41. interface Props extends Omit<ModalProps, "children"> {
  42. warehouse: WarehouseResult[];
  43. stockInLineId: number;
  44. warehouseId: number;
  45. scanner: QrCodeScanner;
  46. addPutAwayHistory: (putAwayData: PutAwayRecord) => void;
  47. onSetDefaultWarehouseId?: (warehouseId: number) => void; // 新增回调
  48. }
  49. const style = {
  50. position: "absolute",
  51. top: "50%",
  52. left: "50%",
  53. transform: "translate(-50%, -50%)",
  54. bgcolor: "background.paper",
  55. pt: { xs: 0.5, sm: 1, md: 1.5 },
  56. px: { xs: 1, sm: 1.5, md: 2 },
  57. pb: { xs: 0.5, sm: 1, md: 1.5 },
  58. width: { xs: "95%", sm: "85%", md: "75%", lg: "70%" },
  59. maxWidth: "900px",
  60. maxHeight: { xs: "98vh", sm: "95vh", md: "90vh" },
  61. overflow: "hidden",
  62. display: "flex",
  63. flexDirection: "column",
  64. };
  65. const scannerStyle = {
  66. position: "absolute",
  67. top: "50%",
  68. left: "50%",
  69. transform: "translate(-50%, -50%)",
  70. bgcolor: "background.paper",
  71. pt: { xs: 2, sm: 3, md: 4 },
  72. px: { xs: 2, sm: 3, md: 4 },
  73. pb: { xs: 6, sm: 7, md: 8 },
  74. width: { xs: "85%", sm: "70%", md: "60%" },
  75. maxWidth: "600px",
  76. };
  77. const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner, addPutAwayHistory, onSetDefaultWarehouseId }) => {
  78. const { t } = useTranslation("putAway");
  79. const [serverError, setServerError] = useState("");
  80. const params = useSearchParams();
  81. const [isOpenScanner, setIsOpenScanner] = useState<boolean>(false);
  82. const [firstWarehouseId, setFirstWarehouseId] = useState<number | null>(null);
  83. const [warehouseMismatchError, setWarehouseMismatchError] = useState<string>("");
  84. const [firstWarehouseInfo, setFirstWarehouseInfo] = useState<{name: string, code: string} | null>(null);
  85. const [itemDefaultWarehouseId, setItemDefaultWarehouseId] = useState<number | null>(null);
  86. const [itemDetail, setItemDetail] = useState<StockInLine>();
  87. const [totalPutAwayQty, setTotalPutAwayQty] = useState<number>(0);
  88. const [unavailableText, setUnavailableText] = useState<string | undefined>(
  89. undefined,
  90. );
  91. const [putQty, setPutQty] = useState<number>(itemDetail?.acceptedQty ?? 0);
  92. const [verified, setVerified] = useState<boolean>(false);
  93. const [qtyError, setQtyError] = useState<string>("");
  94. const defaultNewValue = useMemo(() => {
  95. // console.log("%c ItemDetail", "color:purple", itemDetail);
  96. return (
  97. {
  98. ...itemDetail,
  99. // status: itemDetail.status ?? "pending",
  100. // dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined,
  101. // // putAwayLines: dummyPutAwayLine,
  102. // // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [],
  103. // putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [],
  104. // // qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData],
  105. // escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [],
  106. productionDate: itemDetail?.productionDate ? arrayToDateString(itemDetail?.productionDate, "input") : undefined,
  107. expiryDate: itemDetail?.expiryDate ? arrayToDateString(itemDetail?.expiryDate, "input") : undefined,
  108. receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined,
  109. acceptQty: itemDetail?.acceptedQty ?? 0,
  110. defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1,
  111. } as ModalFormInput
  112. )
  113. }, [itemDetail])
  114. const formProps = useForm<ModalFormInput>({
  115. defaultValues: {
  116. ...defaultNewValue,
  117. },
  118. });
  119. const errors = formProps.formState.errors;
  120. useEffect(() => {
  121. if (itemDetail) {
  122. startScanner();
  123. }
  124. }, [itemDetail])
  125. const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
  126. (...args) => {
  127. setVerified(false);
  128. setItemDetail(undefined);
  129. setTotalPutAwayQty(0);
  130. setItemDefaultWarehouseId(null);
  131. setFirstWarehouseId(null);
  132. setFirstWarehouseInfo(null);
  133. onClose?.(...args);
  134. // reset();
  135. },
  136. [onClose],
  137. );
  138. const scannerCloseHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
  139. (...args) => {
  140. setIsOpenScanner(false);
  141. scanner.stopScan();
  142. console.log("%c Scanning stopped ", "color:cyan");
  143. },
  144. [],
  145. );
  146. const startScanner = () => {
  147. // setIsOpenScanner(true);
  148. scanner.startScan();
  149. console.log("%c Scanning started ", "color:cyan");
  150. };
  151. const openScanner = () => {
  152. setIsOpenScanner(true);
  153. scanner.startScan();
  154. console.log("%c Scanning started ", "color:cyan");
  155. };
  156. // 根据 item 的 locationCode 设置默认 warehouseId
  157. useEffect(() => {
  158. // 直接使用 fetchStockInLineInfo 返回的 locationCode
  159. // 只在第一次上架时(firstWarehouseId === null)设置默认值
  160. if (itemDetail?.locationCode && warehouse.length > 0 && firstWarehouseId === null) {
  161. const locationCode = itemDetail.locationCode;
  162. if (locationCode) {
  163. // 根据 locationCode 查找对应的 warehouse(通过 code 匹配)
  164. const matchedWarehouse = warehouse.find(
  165. (w) => w.code === locationCode || w.code?.toLowerCase() === locationCode?.toLowerCase()
  166. );
  167. if (matchedWarehouse) {
  168. // 只设置用于显示的默认值,不通知父组件
  169. setItemDefaultWarehouseId(matchedWarehouse.id);
  170. console.log("%c Set default warehouse from item locationCode (from API, display only):", "color:green", {
  171. locationCode,
  172. warehouseId: matchedWarehouse.id,
  173. warehouseCode: matchedWarehouse.code
  174. });
  175. } else {
  176. console.log("%c No warehouse found for locationCode:", "color:yellow", locationCode);
  177. }
  178. }
  179. }
  180. }, [itemDetail?.locationCode, warehouse, firstWarehouseId]);
  181. useEffect(() => {
  182. // 只使用实际扫描的 warehouseId,不使用默认值进行验证
  183. if (warehouseId > 0 && firstWarehouseId !== null) {
  184. // 第二次及后续上架:必须使用第一次的仓库
  185. if (warehouseId !== firstWarehouseId) {
  186. const firstWh = warehouse.find((w) => w.id == firstWarehouseId);
  187. const scannedWh = warehouse.find((w) => w.id == warehouseId);
  188. setWarehouseMismatchError("倉庫不匹配!必須使用首次上架的倉庫");
  189. setVerified(false);
  190. } else {
  191. setWarehouseMismatchError("");
  192. if (scanner.isScanning) {
  193. setIsOpenScanner(false);
  194. setVerified(true);
  195. msg("貨倉掃瞄成功!");
  196. scanner.resetScan();
  197. }
  198. }
  199. } else if (warehouseId > 0 && firstWarehouseId === null) {
  200. // 第一次上架 - 只接受扫描的 warehouseId
  201. if (scanner.isScanning) {
  202. setIsOpenScanner(false);
  203. setVerified(true);
  204. msg("貨倉掃瞄成功!");
  205. scanner.resetScan();
  206. }
  207. }
  208. }, [warehouseId, firstWarehouseId, scanner.isScanning]);
  209. const warehouseDisplay = useMemo(() => {
  210. // 优先使用扫描的 warehouseId,如果没有扫描则显示默认值作为建议
  211. const displayWarehouseId = warehouseId > 0
  212. ? warehouseId
  213. : (itemDefaultWarehouseId || firstWarehouseId || 0);
  214. const wh = warehouse.find((w) => w.id == displayWarehouseId) ?? warehouse.find((w) => w.id == 1);
  215. return <>{wh?.name} <br/> [{wh?.code}]</>;
  216. }, [warehouse, warehouseId, itemDefaultWarehouseId, firstWarehouseId, verified]);
  217. // useEffect(() => { // Restart scanner for changing warehouse
  218. // if (warehouseId > 0) {
  219. // scanner.startScan();
  220. // console.log("%c Scanner restarted", "color:cyan");
  221. // }
  222. // }, [isOpenScanner])
  223. useLayoutEffect(() => {
  224. if (itemDetail !== undefined) {
  225. if (itemDetail.status == "received") {
  226. formProps.reset({
  227. ...defaultNewValue
  228. })
  229. const total = itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0;
  230. setPutQty(itemDetail?.acceptedQty - total);
  231. // ✅ Get first warehouse from existing put away lines
  232. const firstPutAwayLine = itemDetail.putAwayLines?.[0];
  233. if (firstPutAwayLine?.warehouseId) {
  234. setFirstWarehouseId(firstPutAwayLine.warehouseId);
  235. // ✅ Store first warehouse info for display
  236. const firstWh = warehouse.find((w) => w.id == firstPutAwayLine.warehouseId);
  237. if (firstWh) {
  238. setFirstWarehouseInfo({
  239. name: firstWh.name || "",
  240. code: firstWh.code || ""
  241. });
  242. }
  243. } else {
  244. setFirstWarehouseId(null);
  245. setFirstWarehouseInfo(null);
  246. }
  247. console.log("%c Loaded data:", "color:lime", defaultNewValue);
  248. } else {
  249. switch (itemDetail.status) {
  250. case "pending": alert("此貨品有待品檢"); break;
  251. case "rejected": alert("此貨品已被拒收"); break;
  252. case "escalated": alert("此貨品已被上報"); break;
  253. case "partially_completed": alert("此貨品已部分上架"); break;
  254. case "completed": alert("此貨品已上架"); break;
  255. default: alert("此貨品暫時無法上架"); break;
  256. }
  257. closeHandler({}, "backdropClick");
  258. }
  259. }
  260. }, [open, itemDetail, defaultNewValue])
  261. const fetchStockInLine = useCallback(
  262. async (stockInLineId: number) => {
  263. setUnavailableText(undefined);
  264. try {
  265. const res = await fetchStockInLineInfo(stockInLineId);
  266. console.log("%c Fetched Stock In Line Info:", "color:gold", res);
  267. const total = res.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0;
  268. setTotalPutAwayQty(total);
  269. setItemDetail(res);
  270. } catch (e) {
  271. console.log("%c Error when fetching Stock In Line: ", "color:red", e);
  272. alert("錯誤的二維碼");
  273. closeHandler({}, "backdropClick");
  274. }
  275. },
  276. [formProps, itemDetail, fetchStockInLineInfo],
  277. );
  278. useEffect(() => {
  279. if (stockInLineId) { fetchStockInLine(stockInLineId); }
  280. }, [stockInLineId]);
  281. const validateQty = useCallback((qty : number = putQty) => {
  282. // if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") {
  283. // setQtyError(t("value must be a number"));
  284. // } else
  285. if (!Number.isInteger(qty)) {
  286. setQtyError(t("value must be integer"));
  287. }
  288. //if (qty > itemDetail?.demandQty!! - totalPutAwayQty) {
  289. //setQtyError(`${t("putQty must not greater than")} ${
  290. // itemDetail?.demandQty!! - totalPutAwayQty}` );
  291. //}
  292. if (qty > itemDetail?.acceptedQty!! - totalPutAwayQty) {
  293. setQtyError(`${t("putQty must not greater than")} ${
  294. itemDetail?.acceptedQty!! - totalPutAwayQty}` );
  295. }
  296. else
  297. // if (qty > itemDetail?.acceptedQty!!) {
  298. // setQtyError(`${t("putQty must not greater than")} ${
  299. // itemDetail?.acceptedQty!!}` );
  300. // } else
  301. if (qty < 1) {
  302. setQtyError(t("minimal value is 1"));
  303. } else {
  304. setQtyError("");
  305. }
  306. console.log("%c Validated putQty:", "color:yellow", putQty);
  307. },[setQtyError, putQty, itemDetail])
  308. const onSubmit = useCallback<SubmitHandler<ModalFormInput>>(
  309. async (data, event) => {
  310. // console.log("errors", errors);
  311. // const lotLine = {
  312. // warehouseId: warehouseId,
  313. // qty: acceptQty;
  314. // }
  315. try {
  316. // 确定最终使用的 warehouseId
  317. const effectiveWarehouseId = warehouseId > 0
  318. ? warehouseId
  319. : (itemDefaultWarehouseId || 0);
  320. if (firstWarehouseId !== null && effectiveWarehouseId !== firstWarehouseId) {
  321. setWarehouseMismatchError("倉庫不匹配!必須使用首次上架的倉庫");
  322. return;
  323. }
  324. const args = {
  325. // ...itemDetail,
  326. id: itemDetail?.id,
  327. purchaseOrderId: itemDetail?.purchaseOrderId,
  328. purchaseOrderLineId: itemDetail?.purchaseOrderLineId,
  329. itemId: itemDetail?.itemId,
  330. acceptedQty: itemDetail?.acceptedQty,
  331. acceptQty: itemDetail?.acceptedQty,
  332. status: "received",
  333. // purchaseOrderId: parseInt(params.get("id")!),
  334. // purchaseOrderLineId: itemDetail?.purchaseOrderLineId,
  335. // itemId: itemDetail?.itemId,
  336. // acceptedQty: data.acceptedQty,
  337. // status: data.status,
  338. // ...data,
  339. // productionDate: productionDate,
  340. // for putaway data
  341. inventoryLotLines: [{
  342. warehouseId: effectiveWarehouseId,
  343. qty: putQty,
  344. }],
  345. // data.putAwayLines?.filter((line) => line._isNew !== false)
  346. } as StockInLineEntry & ModalFormInput;
  347. console.log(args);
  348. // return
  349. // if (formProps.formState.errors) {
  350. // setServerError(t("An error has occurred. Please try again later."));
  351. // return false;
  352. // }
  353. if (qtyError !== "") {
  354. return;
  355. }
  356. console.log("%c Submitting Data:", "color:blue", args);
  357. const res = await updateStockInLine(args);
  358. if (Boolean(res.id)) {
  359. // update entries
  360. console.log("%c Update Success:", "color:green", res);
  361. // add loading
  362. const putAwayData = {
  363. itemName: itemDetail?.itemName,
  364. itemCode: itemDetail?.itemNo,
  365. poCode: itemDetail?.poCode,
  366. joCode: itemDetail?.joCode,
  367. lotNo: itemDetail?.lotNo,
  368. warehouseCode: warehouse.find((w) => w.id == effectiveWarehouseId)?.code,
  369. warehouse: warehouse.find((w) => w.id == effectiveWarehouseId)?.name,
  370. putQty: putQty,
  371. uom: itemDetail?.uom?.udfudesc,
  372. } as PutAwayRecord;
  373. addPutAwayHistory(putAwayData);
  374. msg("貨品上架成功!");
  375. closeHandler({}, "backdropClick");
  376. }
  377. // console.log(res);
  378. // if (res)
  379. } catch (e) {
  380. // server error
  381. setServerError(t("An error has occurred. Please try again later."));
  382. console.log(e);
  383. }
  384. },
  385. [t, itemDetail, putQty, warehouseId, itemDefaultWarehouseId, firstWarehouseId, warehouse],
  386. );
  387. return (
  388. <FormProvider {...formProps}>
  389. <Modal open={open} onClose={closeHandler}>
  390. <Box
  391. sx={style}
  392. component="form"
  393. onSubmit={formProps.handleSubmit(onSubmit)}
  394. >
  395. <Box sx={{ overflow: "hidden", flex: 1, display: "flex", flexDirection: "column" }}>
  396. <Grid container xs={12}>
  397. <Grid item xs={12}>
  398. {itemDetail != undefined ? (
  399. <>
  400. <Stack direction="column" justifyContent="flex-end" gap={0.25} sx={{ mb: 0.5 }}>
  401. <Typography variant="h4" sx={{ fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" }, mb: 0.25, lineHeight: 1.2 }}>
  402. 處理上架
  403. </Typography>
  404. <Box sx={{ "& .MuiFormControl-root": { mb: 0.5 }, "& .MuiTextField-root": { mb: 0.5 }, "& .MuiGrid-item": { mb: 0.25 } }}>
  405. <Grid item xs={12}>
  406. {itemDetail.jobOrderId ? (
  407. <FgStockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/>
  408. ) : (
  409. <StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/>
  410. )}
  411. </Grid>
  412. </Box>
  413. <Paper sx={{ mt: 0.5, padding: { xs: 0.5, sm: 0.75, md: 1 }, width: "100%", backgroundColor: verified ? '#bceb19' : '#FCD34D' }}>
  414. <Grid
  415. container
  416. spacing={{ xs: 0.5, sm: 0.75, md: 1 }}
  417. direction="row"
  418. justifyContent="space-between"
  419. alignItems="stretch"
  420. sx={{ width: '100%' }}
  421. >
  422. <Grid item xs={12} sm={5}>
  423. <Box sx={{
  424. mb: { xs: 0.25, md: 0.5 },
  425. p: { xs: 0.5, sm: 0.75, md: 1 },
  426. backgroundColor: verified ? '#bceb19' : '#FCD34D',
  427. borderRadius: 0,
  428. display: 'flex',
  429. flexDirection: 'row',
  430. gap: 0.25,
  431. flexWrap: 'wrap'
  432. }}
  433. >
  434. <Grid container>
  435. {verified? (
  436. <>
  437. <CheckCircle sx={{
  438. color:"green",
  439. fontSize: { xs: "16px", sm: "20px", md: "24px" },
  440. mr: 0.25
  441. }}/>
  442. <Typography
  443. variant="h5"
  444. component="h2"
  445. sx={{
  446. fontWeight: 'bold',
  447. color: 'black',
  448. fontSize: { xs: "0.75rem", sm: "0.9rem", md: "1rem" },
  449. lineHeight: 1.2
  450. }}
  451. noWrap
  452. >
  453. 掃瞄完成
  454. </Typography>
  455. </>
  456. ) : (
  457. <>
  458. <ErrorOutline sx={{
  459. color:"red",
  460. fontSize: { xs: "16px", sm: "20px", md: "24px" },
  461. mr: 0.25
  462. }}/>
  463. <Typography
  464. variant="h5"
  465. component="h2"
  466. sx={{
  467. fontWeight: 'bold',
  468. color: 'black',
  469. fontSize: { xs: "0.75rem", sm: "0.9rem", md: "1rem" },
  470. lineHeight: 1.2
  471. }}
  472. noWrap
  473. >
  474. {warehouseMismatchError || (firstWarehouseId !== null && warehouseId > 0 && warehouseId !== firstWarehouseId)
  475. ? "倉庫不匹配!請掃瞄首次上架的倉庫"
  476. : "請掃瞄倉庫二維碼"}
  477. </Typography>
  478. </>
  479. )
  480. }
  481. <QrCode sx={{ fontSize: { xs: "16px", sm: "20px", md: "24px" } }}/>
  482. </Grid>
  483. <Grid container>
  484. <Typography
  485. variant="h4"
  486. sx={{
  487. fontWeight: 'bold',
  488. color: 'black',
  489. fontSize: { xs: "0.85rem", sm: "1rem", md: "1.25rem" },
  490. lineHeight: 1.2
  491. }}
  492. noWrap
  493. >
  494. {warehouseDisplay} <Box component="span" sx={{ fontSize: { xs: "0.95rem", sm: "1.2rem", md: "1.5rem" }, color: "black" }}>{verified ? "" : `(建議)`}</Box>
  495. </Typography>
  496. </Grid>
  497. </Box>
  498. </Grid>
  499. <Grid item xs={12} sm={3}>
  500. <Box sx={{
  501. height: '100%',
  502. p: { xs: 0.25, sm: 0.5, md: 0.75 },
  503. textAlign: 'center',
  504. display: "flex",
  505. flexDirection: "column",
  506. justifyContent: "center",
  507. }}>
  508. <TextField
  509. type="number"
  510. label={t("putQty")}
  511. fullWidth
  512. size="small"
  513. sx={{
  514. flex: 1,
  515. "& .MuiInputBase-input": {
  516. padding: { xs: "6px 6px 1px", sm: "8px 8px 2px", md: "10px 10px 3px" },
  517. fontSize: { xs: "16px", sm: "22px", md: "32px" },
  518. fontWeight: "bold",
  519. height: { xs: "32px", sm: "40px", md: "48px" },
  520. },
  521. "& .MuiInputBase-root": {
  522. height: "100%",
  523. borderColor: "black",
  524. },
  525. "& .MuiInputLabel-root": {
  526. fontSize: { xs: "10px", sm: "12px", md: "18px" },
  527. top: "-2px",
  528. color: "black",
  529. },
  530. "& .MuiFormHelperText-root": {
  531. fontSize: { xs: "9px", sm: "10px", md: "14px" },
  532. marginTop: "2px",
  533. lineHeight: "1.1",
  534. },
  535. }}
  536. // defaultValue={itemDetail?.demandQty!! - totalPutAwayQty}
  537. // defaultValue={itemDetail.demandQty}
  538. defaultValue={itemDetail?.acceptedQty!! - totalPutAwayQty}
  539. onChange={(e) => {
  540. const value = e.target.value;
  541. validateQty(Number(value));
  542. setPutQty(Number(value));
  543. }}
  544. onKeyDown={(e) => {
  545. // Prevent non-numeric characters
  546. if (["e", "E", "+", "-", "."].includes(e.key)) {
  547. e.preventDefault();
  548. }
  549. }}
  550. // onBlur={(e) => {
  551. // const value = e.target.value;
  552. // setPutQty(Number(value));
  553. // validateQty(Number(value));
  554. // }}
  555. // disabled={true}
  556. // {...register("acceptedQty", {
  557. // required: "acceptedQty required!",
  558. // })}
  559. error={qtyError !== ""}
  560. helperText={qtyError}
  561. />
  562. </Box>
  563. </Grid>
  564. <Grid item xs={12} sm={4}>
  565. <Box
  566. sx={{
  567. p: { xs: 0.25, sm: 0.5, md: 0.75 },
  568. textAlign: 'center',
  569. height: '100%', // Ensure D stretches to full height
  570. display: 'flex',
  571. alignItems: 'center',
  572. justifyContent: 'center',
  573. }}>
  574. <Button
  575. id="putawaySubmit"
  576. type="submit"
  577. variant="contained"
  578. startIcon={<Check sx={{ fontSize: { xs: "14px", sm: "16px", md: "20px" } }} />}
  579. color="secondary"
  580. size="small"
  581. sx={{
  582. height: "100%",
  583. flex: "0 0 auto",
  584. padding: { xs: "3px 6px", sm: "4px 8px", md: "6px 12px" },
  585. fontSize: { xs: "9px", sm: "11px", md: "18px" },
  586. whiteSpace: "nowrap",
  587. textAlign: "center",
  588. border: "1.5px solid", // Add outline
  589. borderColor: "blue",
  590. minHeight: { xs: "32px", sm: "36px", md: "40px" },
  591. "&:hover": {
  592. borderColor: "grey.200", // Slightly different color on hover
  593. backgroundColor: "secondary.dark", // Darker background on hover
  594. },
  595. "&:disabled": {
  596. borderColor: "grey.400", // Visible outline even when disabled
  597. opacity: 0.7, // Slightly faded but still visible
  598. },
  599. }}
  600. // onClick={formProps.handleSubmit()}
  601. disabled={!verified || qtyError != ""}
  602. >
  603. {t("confirm putaway")}
  604. </Button>
  605. </Box>
  606. {/* <Button
  607. id="scanWarehouse"
  608. variant="contained"
  609. startIcon={<QrCode />}
  610. color="primary"
  611. // sx={{ mx: 3, minWidth : "120px", height: "80px",
  612. // padding: "12px 12px", fontSize: "24px"}}
  613. sx={{
  614. flex: "0 0 auto",
  615. padding: "8px 16px",
  616. fontSize: { xs: "16px", sm: "20px", md: "24px" },
  617. whiteSpace: "nowrap",
  618. textAlign: "center",}}
  619. onClick={openScanner}>
  620. {t("scan warehouse")}
  621. </Button> */}
  622. </Grid>
  623. </Grid>
  624. </Paper>
  625. </Stack>
  626. </>
  627. ) : (
  628. // <ReactQrCodeScanner scannerConfig={scannerConfig} />
  629. <>
  630. <Typography variant="h4" sx={{ fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" } }}>
  631. {t("scan loading")}
  632. </Typography>
  633. <LoadingComponent/>
  634. </>
  635. )}
  636. </Grid>
  637. </Grid>
  638. </Box>
  639. <Modal open={isOpenScanner} onClose={scannerCloseHandler}>
  640. <Box sx={scannerStyle}>
  641. <Typography variant="h4" sx={{
  642. display: 'flex',
  643. flexDirection: 'column',
  644. justifyContent: 'center',
  645. margin: 0,
  646. alignItems: 'center',
  647. textAlign: 'center',
  648. fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" }
  649. }}
  650. >
  651. {t("Please scan warehouse qr code")}
  652. </Typography>
  653. {/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */}
  654. </Box>
  655. </Modal>
  656. </Box>
  657. </Modal>
  658. </FormProvider>
  659. );
  660. };
  661. export default PutAwayModal;