From 1007e4aa555cdd503ca2a3f4dc45e819a56111fc Mon Sep 17 00:00:00 2001 From: Harry Groves Date: Fri, 11 Sep 2026 16:01:41 +0800 Subject: [PATCH] change search --- src/app/(main)/report/itemCodeSearchApi.ts | 3 ++- src/app/(main)/report/page.tsx | 8 +++---- src/app/api/inventory/actions.ts | 4 ++-- src/app/api/inventory/index.ts | 2 +- src/app/utils/clientAuthFetch.ts | 10 ++++++--- .../InventorySearch/InventoryLotLineTable.tsx | 2 +- .../InventorySearch/InventorySearch.tsx | 22 ++++++++++++++----- .../InventorySearch/InventoryTable.tsx | 7 ++++++ src/config/reportConfig.ts | 2 +- src/i18n/en/inventory.json | 1 + src/i18n/zh/inventory.json | 1 + src/lib/featureUsageLog.ts | 1 + 12 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/app/(main)/report/itemCodeSearchApi.ts b/src/app/(main)/report/itemCodeSearchApi.ts index 34e7f0ca..e36be1c4 100644 --- a/src/app/(main)/report/itemCodeSearchApi.ts +++ b/src/app/(main)/report/itemCodeSearchApi.ts @@ -58,9 +58,10 @@ const fetchItemPage = async ( }; /** - * FP-MTMS Version Checklist | Functions Ref. No. 81 | v1.0.0 | 2026-09-10 + * FP-MTMS Version Checklist | Functions Ref. No. 81 | v1.0.1 | 2026-09-11 * Typeahead lookup for report item-code multi-select. * Uses the existing paged item API so we never load the full catalog. + * Hits are unique by item code — multi-UoM inventory buckets are expanded only in the downloaded report. */ export const searchItemCodes = async ( query: string, diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index 6dec1c10..f08a79bf 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -419,10 +419,10 @@ export default function ReportPage() { const response = await clientAuthFetch(excelUrl, { method: 'GET', - headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, + skipAuthRedirect: true, + headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/json' }, }); - if (response.status === 401 || response.status === 403) return; if (response.status === 204) { setShowNoDataDialog(true); return; @@ -482,10 +482,10 @@ export default function ReportPage() { const response = await clientAuthFetch(url, { method: 'GET', - headers: { 'Accept': 'application/pdf' }, + skipAuthRedirect: true, + headers: { Accept: 'application/pdf, application/json' }, }); - if (response.status === 401 || response.status === 403) return; if (!response.ok) { const errorText = await response.text(); console.error("Response error:", errorText); diff --git a/src/app/api/inventory/actions.ts b/src/app/api/inventory/actions.ts index 3330a8f0..44e374f3 100644 --- a/src/app/api/inventory/actions.ts +++ b/src/app/api/inventory/actions.ts @@ -179,8 +179,8 @@ async function fetchInventoriesLatestImpl(data: SearchInventory) { export const fetchInventories = cache(fetchInventoriesImpl); /** - * FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10 - * Inventory search page: latest inventory row per item + stock UoM, with optional location filters. + * FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.5 | 2026-09-11 + * Inventory search page: one row per item + stock UoM, with optional location filters. */ export const fetchInventoriesLatest = cache(fetchInventoriesLatestImpl); diff --git a/src/app/api/inventory/index.ts b/src/app/api/inventory/index.ts index 2de389a9..4e09137c 100644 --- a/src/app/api/inventory/index.ts +++ b/src/app/api/inventory/index.ts @@ -5,7 +5,7 @@ import { cache } from "react"; import "server-only"; export interface InventoryResult { - id: number; + id: number | string; itemId: number; itemCode: string; itemName: string; diff --git a/src/app/utils/clientAuthFetch.ts b/src/app/utils/clientAuthFetch.ts index 67ed99a9..6bcc0a2d 100644 --- a/src/app/utils/clientAuthFetch.ts +++ b/src/app/utils/clientAuthFetch.ts @@ -9,7 +9,7 @@ import { LOGIN_SESSION_EXPIRED_HREF } from "@/app/utils/authToken"; */ export async function clientAuthFetch( input: RequestInfo | URL, - init?: RequestInit + init?: RequestInit & { skipAuthRedirect?: boolean } ): Promise { const token = typeof window !== "undefined" ? localStorage.getItem("accessToken") : null; @@ -18,9 +18,13 @@ export async function clientAuthFetch( headers.set("Authorization", `Bearer ${token}`); } - const response = await fetch(input, { ...init, headers }); + const { skipAuthRedirect, ...fetchInit } = init ?? {}; + const response = await fetch(input, { ...fetchInit, headers }); - if (response.status === 401 || response.status === 403) { + if ( + !skipAuthRedirect && + (response.status === 401 || response.status === 403) + ) { if (typeof window !== "undefined") { console.warn(`Auth error ${response.status} → redirecting to login`); window.location.href = LOGIN_SESSION_EXPIRED_HREF; diff --git a/src/components/InventorySearch/InventoryLotLineTable.tsx b/src/components/InventorySearch/InventoryLotLineTable.tsx index 237112dc..c87ae886 100644 --- a/src/components/InventorySearch/InventoryLotLineTable.tsx +++ b/src/components/InventorySearch/InventoryLotLineTable.tsx @@ -584,7 +584,7 @@ const prevAdjustmentModalOpenRef = useRef(false); return <> - {inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")} + {inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)}) | ${t("UoM ID")}: ${inventory.uomId ?? "-"} | ${inventory.uomUdfudesc || inventory.uomShortDesc || inventory.uomCode || "-"}` : t("No items are selected yet.")} {inventory && canStockAdjust && ( { return []; }; -/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.5 | 2026-09-11 */ const InventorySearch: React.FC = ({ inventories, printerCombo, @@ -84,6 +87,11 @@ const InventorySearch: React.FC = ({ const canStockAdjust = hasAbility(abilities, AUTH.INVENTORY_ADJUST); const searchInFlightRef = useRef(false); + const withUniqueInventoryRowId = useCallback((row: InventoryResult): InventoryResult => ({ + ...row, + id: `${row.itemId}-${row.uomId ?? 0}-${row.id}`, + }), []); + const buildSyntheticInventory = useCallback((item: ItemLookupRow): InventoryResult => { const uom = item.uomDesc || item.uom || item.purchaseUnit || ''; return { @@ -134,7 +142,7 @@ const InventorySearch: React.FC = ({ const applyItemsAsSyntheticInventories = useCallback( (items: ItemLookupRow[]) => { if (!items.length) return false; - const synthetics = items.map(buildSyntheticInventory); + const synthetics = items.map(buildSyntheticInventory).map(withUniqueInventoryRowId); setFilteredInventories(synthetics); setInventoriesTotalCount(synthetics.length); setSelectedInventory(synthetics[0]); @@ -142,7 +150,7 @@ const InventorySearch: React.FC = ({ setInventoryLotLinesPagingController(() => defaultPagingController); return true; }, - [buildSyntheticInventory], + [buildSyntheticInventory, withUniqueInventoryRowId], ); // Scan-mode UI (hardware QR scanner via QrCodeScannerProvider) @@ -253,23 +261,25 @@ const InventorySearch: React.FC = ({ const response = await fetchInventoriesLatest(params); if (response) { + const records = (response.records ?? []).map(withUniqueInventoryRowId); setInventoriesTotalCount(response.total); switch (actionType) { case 'init': case 'reset': case 'search': - setFilteredInventories(() => response.records); + setFilteredInventories(() => records); break; case 'paging': setFilteredInventories((fi) => - uniqBy([...fi, ...response.records], (row) => `${row.itemId}-${row.uomId ?? row.uomUdfudesc ?? ''}`), + uniqBy([...fi, ...records], (row) => `${row.itemId}-${row.uomId ?? row.uomUdfudesc ?? ''}`), ); } + return { ...response, records }; } return response; }, - [enableLocationFilter, location, withLocationParams], + [enableLocationFilter, location, withLocationParams, withUniqueInventoryRowId], ); useEffect(() => { diff --git a/src/components/InventorySearch/InventoryTable.tsx b/src/components/InventorySearch/InventoryTable.tsx index 6f987677..9c5435ae 100644 --- a/src/components/InventorySearch/InventoryTable.tsx +++ b/src/components/InventorySearch/InventoryTable.tsx @@ -39,6 +39,13 @@ const InventoryTable: React.FC = ({ inventories, pagingController, setPag headerAlign: "right", type: "integer", }, + { + name: "uomId", + label: t("UoM ID"), + align: "right", + headerAlign: "right", + renderCell: (params) => params.uomId ?? "-", + }, { name: "uomUdfudesc", label: t("Stock UoM"), diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts index c6ed8b5b..f0d41147 100644 --- a/src/config/reportConfig.ts +++ b/src/config/reportConfig.ts @@ -332,7 +332,7 @@ export const REPORTS: ReportDefinition[] = [ ] }, */ - /** FP-MTMS Version Checklist | Functions Ref. No. 82 | v1.0.0 | 2026-09-10 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 82 | v1.0.1 | 2026-09-11 */ { id: "rep-007", title: "庫存結餘報告", diff --git a/src/i18n/en/inventory.json b/src/i18n/en/inventory.json index 5d170b12..8ce4d49b 100644 --- a/src/i18n/en/inventory.json +++ b/src/i18n/en/inventory.json @@ -78,6 +78,7 @@ "Target Location": "Target Location", "Type": "Type", "UoM": "UoM", + "UoM ID": "UoM ID", "WIP": "Work in progress", "Warehouse": "Warehouse", "cmb": "Consumables", diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index 8513855b..675464cb 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -78,6 +78,7 @@ "Target Location": "目標倉位", "Type": "類型", "UoM": "單位", + "UoM ID": "單位ID", "WIP": "半成品", "Warehouse": "倉庫", "cmb": "消耗品", diff --git a/src/lib/featureUsageLog.ts b/src/lib/featureUsageLog.ts index 64e527db..3bc93dce 100644 --- a/src/lib/featureUsageLog.ts +++ b/src/lib/featureUsageLog.ts @@ -58,6 +58,7 @@ export function logFeatureUsage( const res = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/feature-usage/log`, { method: "POST", headers: { "Content-Type": "application/json" }, + skipAuthRedirect: true, body: JSON.stringify({ featureCode, actionType,