"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Box, Paper, Typography } from "@mui/material"; import type { Result } from "@zxing/library"; import ReactQrCodeScanner, { ScannerConfig, defaultScannerConfig, } from "../ReactQrCodeScanner/ReactQrCodeScanner"; import { WarehouseResult } from "@/app/api/warehouse"; import { useTranslation } from "react-i18next"; import { QrCodeScanner as QrCodeIcon } from "@mui/icons-material"; import PutAwayModal from "./PutAwayModal"; import PutAwayReviewGrid from "./PutAwayReviewGrid"; import type { PutAwayRecord } from "."; import type { QrCodeScanner as QrCodeScannerType } from "../QrCodeScannerProvider/QrCodeScannerProvider"; /** Find first number after a keyword in a string (e.g. "StockInLine" or "warehouseId"). */ function findIdByRoughMatch(inputString: string, keyword: string): number | null { const idx = inputString.indexOf(keyword); if (idx === -1) return null; const after = inputString.slice(idx + keyword.length); const match = after.match(/\d+/); return match ? parseInt(match[0], 10) : null; } type Props = { warehouse: WarehouseResult[]; }; type ScanStatusType = "pending" | "scanning"; const dummyScanner: QrCodeScannerType = { values: [], isScanning: false, startScan: () => {}, stopScan: () => {}, resetScan: () => {}, result: undefined, state: "pending", error: undefined, }; const PutAwayCamScan: React.FC = ({ warehouse }) => { const { t } = useTranslation("putAway"); const [scanStatus, setScanStatus] = useState("pending"); const [openPutAwayModal, setOpenPutAwayModal] = useState(false); const [scannedSilId, setScannedSilId] = useState(0); const [scannedWareHouseId, setScannedWareHouseId] = useState(0); const [putAwayHistory, setPutAwayHistory] = useState([]); const addPutAwayHistory = (putAwayData: PutAwayRecord) => { const newPutaway = { ...putAwayData, id: putAwayHistory.length + 1 }; setPutAwayHistory((prev) => [...prev, newPutaway]); }; const handleSetDefaultWarehouseId = useCallback((warehouseId: number) => { if (scannedWareHouseId === 0) { setScannedWareHouseId(warehouseId); } }, [scannedWareHouseId]); // Refs so the scanner (which only gets config on mount) always calls the latest handler and we throttle duplicates const handleScanRef = useRef<(rawText: string) => void>(() => {}); const lastScannedRef = useRef({ text: "", at: 0 }); const THROTTLE_MS = 2000; const handleScan = useCallback( (rawText: string) => { const trimmed = (rawText || "").trim(); if (!trimmed) return; const now = Date.now(); if ( lastScannedRef.current.text === trimmed && now - lastScannedRef.current.at < THROTTLE_MS ) { return; } setScanStatus("scanning"); const done = () => { lastScannedRef.current = { text: trimmed, at: now }; }; const trySetSilId = (num: number): boolean => { if (!Number.isFinite(num) || num <= 0) return false; setScannedSilId(num); done(); return true; }; const trySetWarehouseId = (num: number): boolean => { if (!Number.isFinite(num) || num <= 0) return false; setScannedWareHouseId(num); done(); return true; }; const isFirstScan = scannedSilId === 0; const isSecondScan = scannedSilId > 0 && scannedWareHouseId === 0; // 1) Try JSON try { const data = JSON.parse(trimmed) as Record; if (data && typeof data === "object") { if (isFirstScan) { if (data.stockInLineId != null && trySetSilId(Number(data.stockInLineId))) return; if (data.value != null && trySetSilId(Number(data.value))) return; } if (isSecondScan) { if (data.warehouseId != null && trySetWarehouseId(Number(data.warehouseId))) return; if (data.value != null && trySetWarehouseId(Number(data.value))) return; } } } catch { // not JSON } // 2) Rough match: "StockInLine" or "warehouseId" + number (same as barcode scanner) if (isFirstScan) { const sil = findIdByRoughMatch(trimmed, "StockInLine") ?? findIdByRoughMatch(trimmed, "stockInLineId"); if (sil != null && trySetSilId(sil)) return; } if (isSecondScan) { const wh = findIdByRoughMatch(trimmed, "warehouseId") ?? findIdByRoughMatch(trimmed, "WarehouseId"); if (wh != null && trySetWarehouseId(wh)) return; } // 3) Plain number const num = Number(trimmed); if (isFirstScan && trySetSilId(num)) return; if (isSecondScan && trySetWarehouseId(num)) return; }, [scannedSilId, scannedWareHouseId], ); handleScanRef.current = handleScan; // Open modal only after both stock-in-line and location (warehouse) are scanned useEffect(() => { if (scannedSilId > 0 && scannedWareHouseId > 0) { setOpenPutAwayModal(true); setScanStatus("pending"); } }, [scannedSilId, scannedWareHouseId]); const closeModal = () => { setScannedSilId(0); setScannedWareHouseId(0); setOpenPutAwayModal(false); setScanStatus("pending"); }; const displayText = useMemo(() => { if (scanStatus === "scanning") { return t("Scanning"); } if (scannedSilId > 0 && scannedWareHouseId > 0) { return t("Scanned, opening detail"); } if (scannedSilId > 0) { return t("Please scan warehouse qr code"); } return t("Pending scan"); }, [scanStatus, scannedSilId, scannedWareHouseId, t]); const scannerConfig: ScannerConfig = useMemo( () => ({ ...defaultScannerConfig, onUpdate: (_err: unknown, result?: Result): void => { if (result) { handleScanRef.current(result.getText()); } }, }), [], ); return ( <> {displayText} {putAwayHistory.length > 0 && ( <> {t("putAwayHistory")} )} ); }; export default PutAwayCamScan;