瀏覽代碼

better UX in inventory search

MergeProblem1
kelvin.yau 3 天之前
父節點
當前提交
6fe4889b02
共有 1 個文件被更改,包括 100 次插入11 次删除
  1. +100
    -11
      src/components/InventorySearch/InventorySearch.tsx

+ 100
- 11
src/components/InventorySearch/InventorySearch.tsx 查看文件

@@ -16,7 +16,7 @@ import {
fetchInventoryLotLines, fetchInventoryLotLines,
} from '@/app/api/inventory/actions'; } from '@/app/api/inventory/actions';
import { PrinterCombo } from '@/app/api/settings/printer'; import { PrinterCombo } from '@/app/api/settings/printer';
import { ItemCombo, fetchItemsWithDetails } from '@/app/api/settings/item/actions';
import { ItemCombo, fetchItemsWithDetails, ItemWithDetails } from '@/app/api/settings/item/actions';
import { import {
Button, Button,
Dialog, Dialog,
@@ -59,6 +59,38 @@ type SearchParamNames = keyof SearchQuery;
const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => { const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
const { t } = useTranslation(['inventory', 'common', 'item']); const { t } = useTranslation(['inventory', 'common', 'item']);


const buildSyntheticInventory = useCallback(
(item: ItemWithDetails): InventoryResult => ({
id: 0,
itemId: item.id,
itemCode: item.code,
itemName: item.name,
itemType: 'Material',
onHandQty: 0,
onHoldQty: 0,
unavailableQty: 0,
availableQty: 0,
uomCode: item.uom,
uomUdfudesc: item.uomDesc,
uomShortDesc: item.uom,
qtyPerSmallestUnit: 1,
baseUom: item.uom,
price: 0,
currencyName: '',
status: 'active',
latestMarketUnitPrice: undefined,
latestMupUpdatedDate: undefined,
}),
[],
);

const getFirstItemRecord = useCallback((res: any): ItemWithDetails | null => {
if (!res) return null;
if (Array.isArray(res)) return (res[0] as ItemWithDetails) ?? null;
if (Array.isArray(res?.records)) return (res.records[0] as ItemWithDetails) ?? null;
return null;
}, []);



// Inventory // Inventory
const [filteredInventories, setFilteredInventories] = useState<InventoryResult[]>([]); const [filteredInventories, setFilteredInventories] = useState<InventoryResult[]>([]);
@@ -260,21 +292,48 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {


// On Search // On Search
const onSearch = useCallback( const onSearch = useCallback(
(query: Record<SearchParamNames, string>) => {
async (query: Record<SearchParamNames, string>) => {
setLotNoFilter(''); setLotNoFilter('');
setScannedItemId(null); setScannedItemId(null);
setScanUiMode('idle'); setScanUiMode('idle');
setScanHoverCancel(false); setScanHoverCancel(false);
qrScanner.stopScan(); qrScanner.stopScan();
qrScanner.resetScan(); qrScanner.resetScan();
refetchInventoryData(query, 'search', defaultPagingController, '');
refetchInventoryLotLineData(null, 'search', defaultPagingController);
const invRes = await refetchInventoryData(query, 'search', defaultPagingController, '');
await refetchInventoryLotLineData(null, 'search', defaultPagingController);


setInputs(() => query); setInputs(() => query);
setInventoriesPagingController(() => defaultPagingController); setInventoriesPagingController(() => defaultPagingController);
setInventoryLotLinesPagingController(() => defaultPagingController); setInventoryLotLinesPagingController(() => defaultPagingController);

// If there are no inventory rows, render a synthetic inventory so the "Stock Adjustment" chip can be used.
if (invRes?.records?.length === 0) {
try {
const code = query.itemCode?.trim?.();
const name = query.itemName?.trim?.();
const lookupParams = code ? { code } : name ? { name } : null;

if (lookupParams) {
const itemRes = await fetchItemsWithDetails(lookupParams);
const firstItem = getFirstItemRecord(itemRes);
if (firstItem) {
setSelectedInventory(buildSyntheticInventory(firstItem));
setFilteredInventoryLotLines([]);
setInventoryLotLinesPagingController(() => defaultPagingController);
}
}
} catch (e) {
console.error('Failed to build synthetic inventory:', e);
}
}
}, },
[qrScanner, refetchInventoryData, refetchInventoryLotLineData],
[
qrScanner,
refetchInventoryData,
refetchInventoryLotLineData,
buildSyntheticInventory,
getFirstItemRecord,
],
); );


const startLotScan = useCallback(() => { const startLotScan = useCallback(() => {
@@ -319,7 +378,16 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
onInventoryRowClick(target); onInventoryRowClick(target);
} else { } else {
refetchInventoryLotLineData(null, 'search', defaultPagingController); refetchInventoryLotLineData(null, 'search', defaultPagingController);
setSelectedInventory(null);
// No inventory rows for this scanned item => show synthetic inventory with the existing chip workflow.
const itemRes = await fetchItemsWithDetails({ code: res?.itemCode });
const firstItem = getFirstItemRecord(itemRes);
if (firstItem) {
setSelectedInventory(buildSyntheticInventory(firstItem));
setFilteredInventoryLotLines([]);
setInventoryLotLinesPagingController(() => defaultPagingController);
} else {
setSelectedInventory(null);
}
} }


setInventoriesPagingController(() => defaultPagingController); setInventoriesPagingController(() => defaultPagingController);
@@ -338,6 +406,8 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
qrScanner.result, qrScanner.result,
refetchInventoryData, refetchInventoryData,
refetchInventoryLotLineData, refetchInventoryLotLineData,
buildSyntheticInventory,
getFirstItemRecord,
scanUiMode, scanUiMode,
]); ]);


@@ -471,6 +541,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
variant="outlined" variant="outlined"
color="secondary" color="secondary"
onClick={handleOpenOpeningInventoryModal} onClick={handleOpenOpeningInventoryModal}
sx={{ display: 'none' }}
> >
{t('Add entry for items without inventory')} {t('Add entry for items without inventory')}
</Button> </Button>
@@ -499,13 +570,31 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
inventoryLotLinesPagingController, inventoryLotLinesPagingController,
) )
} }
onStockAdjustmentSuccess={() =>
refetchInventoryLotLineData(
selectedInventory?.itemId ?? null,
onStockAdjustmentSuccess={async () => {
const itemId = selectedInventory?.itemId ?? null;

// Refresh both blocks:
// - middle: InventoryTable (inventories list)
// - bottom: InventoryLotLineTable (lot lines for selected item)
const invRes = await refetchInventoryData(
inputs,
'search',
inventoriesPagingController,
lotNoFilter,
);

await refetchInventoryLotLineData(
itemId,
'search', 'search',
inventoryLotLinesPagingController, inventoryLotLinesPagingController,
)
}
);

// If inventory becomes available again after OPEN/ADJ, sync selected row.
if (itemId != null && invRes?.records?.length) {
const target = invRes.records.find((r) => r.itemId === itemId);
if (target) setSelectedInventory(target);
}
}}
/> />


<Dialog <Dialog


Loading…
取消
儲存