|
|
|
@@ -7,7 +7,9 @@ import { uniq, uniqBy } from 'lodash'; |
|
|
|
import InventoryTable from './InventoryTable'; |
|
|
|
import { defaultPagingController } from '../SearchResults/SearchResults'; |
|
|
|
import InventoryLotLineTable from './InventoryLotLineTable'; |
|
|
|
import { useQrCodeScannerContext } from '@/components/QrCodeScannerProvider/QrCodeScannerProvider'; |
|
|
|
import { |
|
|
|
analyzeQrCode, |
|
|
|
SearchInventory, |
|
|
|
SearchInventoryLotLine, |
|
|
|
fetchInventories, |
|
|
|
@@ -68,6 +70,15 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
const [inventoryLotLinesPagingController, setInventoryLotLinesPagingController] = useState(defaultPagingController) |
|
|
|
const [inventoryLotLinesTotalCount, setInventoryLotLinesTotalCount] = useState(0) |
|
|
|
|
|
|
|
// Scan-mode UI (hardware QR scanner via QrCodeScannerProvider) |
|
|
|
const qrScanner = useQrCodeScannerContext(); |
|
|
|
const [scanUiMode, setScanUiMode] = useState<'idle' | 'scanning'>('idle'); |
|
|
|
const [scanHoverCancel, setScanHoverCancel] = useState(false); |
|
|
|
|
|
|
|
// Resolved lot no for filtering |
|
|
|
const [lotNoFilter, setLotNoFilter] = useState(''); |
|
|
|
const [scannedItemId, setScannedItemId] = useState<number | null>(null); |
|
|
|
|
|
|
|
// Opening inventory (pure opening stock for items without existing inventory) |
|
|
|
const [openingItems, setOpeningItems] = useState<ItemCombo[]>([]); |
|
|
|
const [openingModalOpen, setOpeningModalOpen] = useState(false); |
|
|
|
@@ -122,6 +133,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
query: Record<SearchParamNames, string>, |
|
|
|
actionType: 'reset' | 'search' | 'paging' | 'init', |
|
|
|
pagingController: typeof defaultPagingController, |
|
|
|
lotNo: string, |
|
|
|
) => { |
|
|
|
console.log('%c Action Type 1.', 'color:red', actionType); |
|
|
|
// Avoid loading data again |
|
|
|
@@ -134,6 +146,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
code: query?.itemCode ?? '', |
|
|
|
name: query?.itemName ?? '', |
|
|
|
type: query?.itemType.toLowerCase() === 'all' ? '' : query?.itemType ?? '', |
|
|
|
lotNo: lotNo?.trim() ? lotNo.trim() : undefined, |
|
|
|
pageNum: pagingController.pageNum - 1, |
|
|
|
pageSize: pagingController.pageSize, |
|
|
|
}; |
|
|
|
@@ -154,19 +167,21 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return response; |
|
|
|
}, |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
refetchInventoryData(defaultInputs, 'init', defaultPagingController); |
|
|
|
refetchInventoryData(defaultInputs, 'init', defaultPagingController, ''); |
|
|
|
}, [defaultInputs, refetchInventoryData]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
// if (!isEqual(inventoriesPagingController, defaultPagingController)) { |
|
|
|
refetchInventoryData(inputs, 'paging', inventoriesPagingController) |
|
|
|
refetchInventoryData(inputs, 'paging', inventoriesPagingController, lotNoFilter) |
|
|
|
// } |
|
|
|
}, [inventoriesPagingController]) |
|
|
|
}, [inventoriesPagingController, inputs, lotNoFilter, refetchInventoryData]) |
|
|
|
|
|
|
|
// Inventory Lot Line |
|
|
|
const refetchInventoryLotLineData = useCallback( |
|
|
|
@@ -217,14 +232,20 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
|
|
|
|
// Reset |
|
|
|
const onReset = useCallback(() => { |
|
|
|
refetchInventoryData(defaultInputs, 'reset', defaultPagingController); |
|
|
|
refetchInventoryData(defaultInputs, 'reset', defaultPagingController, ''); |
|
|
|
refetchInventoryLotLineData(null, 'reset', defaultPagingController); |
|
|
|
// setFilteredInventories(inventories); |
|
|
|
|
|
|
|
setLotNoFilter(''); |
|
|
|
setScannedItemId(null); |
|
|
|
setScanUiMode('idle'); |
|
|
|
setScanHoverCancel(false); |
|
|
|
qrScanner.stopScan(); |
|
|
|
qrScanner.resetScan(); |
|
|
|
setInputs(() => defaultInputs) |
|
|
|
setInventoriesPagingController(() => defaultPagingController) |
|
|
|
setInventoryLotLinesPagingController(() => defaultPagingController) |
|
|
|
}, []); |
|
|
|
}, [defaultInputs, qrScanner, refetchInventoryData, refetchInventoryLotLineData]); |
|
|
|
|
|
|
|
// Click Row |
|
|
|
const onInventoryRowClick = useCallback( |
|
|
|
@@ -239,16 +260,86 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
// On Search |
|
|
|
const onSearch = useCallback( |
|
|
|
(query: Record<SearchParamNames, string>) => { |
|
|
|
refetchInventoryData(query, 'search', defaultPagingController); |
|
|
|
setLotNoFilter(''); |
|
|
|
setScannedItemId(null); |
|
|
|
setScanUiMode('idle'); |
|
|
|
setScanHoverCancel(false); |
|
|
|
qrScanner.stopScan(); |
|
|
|
qrScanner.resetScan(); |
|
|
|
refetchInventoryData(query, 'search', defaultPagingController, ''); |
|
|
|
refetchInventoryLotLineData(null, 'search', defaultPagingController); |
|
|
|
|
|
|
|
setInputs(() => query); |
|
|
|
setInventoriesPagingController(() => defaultPagingController); |
|
|
|
setInventoryLotLinesPagingController(() => defaultPagingController); |
|
|
|
}, |
|
|
|
[refetchInventoryData, refetchInventoryLotLineData], |
|
|
|
[qrScanner, refetchInventoryData, refetchInventoryLotLineData], |
|
|
|
); |
|
|
|
|
|
|
|
const startLotScan = useCallback(() => { |
|
|
|
setScanHoverCancel(false); |
|
|
|
setScanUiMode('scanning'); |
|
|
|
qrScanner.resetScan(); |
|
|
|
qrScanner.startScan(); |
|
|
|
}, [qrScanner]); |
|
|
|
|
|
|
|
const cancelLotScan = useCallback(() => { |
|
|
|
qrScanner.stopScan(); |
|
|
|
qrScanner.resetScan(); |
|
|
|
setScanUiMode('idle'); |
|
|
|
setScanHoverCancel(false); |
|
|
|
}, [qrScanner]); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (scanUiMode !== 'scanning') return; |
|
|
|
|
|
|
|
const itemId = qrScanner.result?.itemId; |
|
|
|
const stockInLineId = qrScanner.result?.stockInLineId; |
|
|
|
if (!itemId || !stockInLineId) return; |
|
|
|
|
|
|
|
(async () => { |
|
|
|
try { |
|
|
|
const res = await analyzeQrCode({ |
|
|
|
itemId: Number(itemId), |
|
|
|
stockInLineId: Number(stockInLineId), |
|
|
|
}); |
|
|
|
|
|
|
|
const resolvedLotNo = res?.scanned?.lotNo?.trim?.() ? res.scanned.lotNo.trim() : ''; |
|
|
|
if (!resolvedLotNo) return; |
|
|
|
|
|
|
|
setLotNoFilter(resolvedLotNo); |
|
|
|
setScannedItemId(res?.itemId ?? Number(itemId)); |
|
|
|
|
|
|
|
const invRes = await refetchInventoryData(inputs, 'search', defaultPagingController, resolvedLotNo); |
|
|
|
const records = invRes?.records ?? []; |
|
|
|
const target = records.find((r) => r.itemId === (res?.itemId ?? Number(itemId))) ?? null; |
|
|
|
|
|
|
|
if (target) { |
|
|
|
onInventoryRowClick(target); |
|
|
|
} else { |
|
|
|
refetchInventoryLotLineData(null, 'search', defaultPagingController); |
|
|
|
setSelectedInventory(null); |
|
|
|
} |
|
|
|
|
|
|
|
setInventoriesPagingController(() => defaultPagingController); |
|
|
|
setInventoryLotLinesPagingController(() => defaultPagingController); |
|
|
|
} catch (e) { |
|
|
|
console.error('Failed to analyze QR code:', e); |
|
|
|
} finally { |
|
|
|
// Always go back to initial state after a scan attempt |
|
|
|
cancelLotScan(); |
|
|
|
} |
|
|
|
})(); |
|
|
|
}, [ |
|
|
|
cancelLotScan, |
|
|
|
inputs, |
|
|
|
onInventoryRowClick, |
|
|
|
qrScanner.result, |
|
|
|
refetchInventoryData, |
|
|
|
refetchInventoryLotLineData, |
|
|
|
scanUiMode, |
|
|
|
]); |
|
|
|
|
|
|
|
console.log('', 'color: #666', inventoriesPagingController); |
|
|
|
|
|
|
|
const handleOpenOpeningInventoryModal = useCallback(() => { |
|
|
|
@@ -360,13 +451,30 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
}} |
|
|
|
onReset={onReset} |
|
|
|
extraActions={ |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
color="secondary" |
|
|
|
onClick={handleOpenOpeningInventoryModal} |
|
|
|
> |
|
|
|
{t('Add entry for items without inventory')} |
|
|
|
</Button> |
|
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}> |
|
|
|
{scanUiMode === 'idle' ? ( |
|
|
|
<Button variant="contained" onClick={startLotScan}> |
|
|
|
{t('Search lot by QR code')} |
|
|
|
</Button> |
|
|
|
) : ( |
|
|
|
<> |
|
|
|
<Button variant="contained" disabled sx={{ bgcolor: 'grey.400', color: 'grey.800' }}> |
|
|
|
{t('Please scan...')} |
|
|
|
</Button> |
|
|
|
<Button variant="contained" color="error" onClick={cancelLotScan}> |
|
|
|
{t('Stop QR Scan')} |
|
|
|
</Button> |
|
|
|
</> |
|
|
|
)} |
|
|
|
|
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
color="secondary" |
|
|
|
onClick={handleOpenOpeningInventoryModal} |
|
|
|
> |
|
|
|
{t('Add entry for items without inventory')} |
|
|
|
</Button> |
|
|
|
</Box> |
|
|
|
} |
|
|
|
/> |
|
|
|
<InventoryTable |
|
|
|
@@ -382,6 +490,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { |
|
|
|
setPagingController={setInventoryLotLinesPagingController} |
|
|
|
totalCount={inventoryLotLinesTotalCount} |
|
|
|
inventory={selectedInventory} |
|
|
|
filterLotNo={lotNoFilter} |
|
|
|
printerCombo={printerCombo ?? []} |
|
|
|
onStockTransferSuccess={() => |
|
|
|
refetchInventoryLotLineData( |
|
|
|
|