Sfoglia il codice sorgente

Revert inventory Location Search; restore latest-inventory one-row-per-item search.

Keep report item-code typeahead and multi-value filters unchanged.

Co-authored-by: Cursor <[email protected]>
production
CANCERYS\kw093 8 ore fa
parent
commit
dc6ae49658
10 ha cambiato i file con 39 aggiunte e 460 eliminazioni
  1. +2
    -10
      src/app/api/inventory/actions.ts
  2. +0
    -1
      src/app/api/inventory/index.ts
  3. +4
    -10
      src/components/InventorySearch/InventoryLotLineTable.tsx
  4. +30
    -123
      src/components/InventorySearch/InventorySearch.tsx
  5. +0
    -54
      src/components/InventorySearch/InventorySearchPage.tsx
  6. +3
    -12
      src/components/InventorySearch/InventorySearchWrapper.tsx
  7. +0
    -229
      src/components/InventorySearch/LocationFilterBar.tsx
  8. +0
    -5
      src/components/SearchBox/SearchBox.tsx
  9. +0
    -8
      src/i18n/en/inventory.json
  10. +0
    -8
      src/i18n/zh/inventory.json

+ 2
- 10
src/app/api/inventory/actions.ts Vedi File

@@ -28,12 +28,8 @@ export interface LotLineInfo {

export interface SearchInventoryLotLine extends Pageable {
itemId: number;
uomId?: number;
/** Non-expired lots with in > out; includes available and unavailable. */
stockIssueBadItem?: boolean;
storeId?: string;
warehouse?: string;
area?: string;
}

export interface SearchStockIssueBadItemLotLine extends Pageable {
@@ -48,9 +44,6 @@ export interface SearchInventory extends Pageable {
name: string;
type: string;
lotNo?: string;
storeId?: string;
warehouse?: string;
area?: string;
}

export interface InventoryResultByPage {
@@ -179,8 +172,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.3 | 2026-09-07
* Inventory search page: latest inventory row per item (no baseUnit/uomId filter).
*/
export const fetchInventoriesLatest = cache(fetchInventoriesLatestImpl);

@@ -202,7 +195,6 @@ async function fetchInventoryLotLinesImpl(data: SearchInventoryLotLine) {
);
}

/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10 */
export const fetchInventoryLotLines = cache(fetchInventoryLotLinesImpl);

/** Bypass React cache() after mutations so lists show fresh qty. */


+ 0
- 1
src/app/api/inventory/index.ts Vedi File

@@ -14,7 +14,6 @@ export interface InventoryResult {
onHoldQty: number;
unavailableQty: number;
availableQty: number;
uomId?: number;
uomCode: string;
uomUdfudesc: string;
uomShortDesc: string;


+ 4
- 10
src/components/InventorySearch/InventoryLotLineTable.tsx Vedi File

@@ -54,18 +54,15 @@ interface Props {
totalCount: number;
inventory: InventoryResult | null;
filterLotNo?: string;
/** Location search: show only the slot (e.g. 00), not the full warehouse code. */
warehouseDisplay?: "full" | "slot";
onStockTransferSuccess?: () => void | Promise<void>;
printerCombo?: PrinterCombo[];
onStockAdjustmentSuccess?: () => void | Promise<void>;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.8 | 2026-09-10 */
/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.7 | 2026-09-08 */
const InventoryLotLineTable: React.FC<Props> = ({
inventoryLotLines, pagingController, setPagingController, totalCount, inventory,
filterLotNo,
warehouseDisplay = "full",
onStockTransferSuccess, printerCombo = [],
onStockAdjustmentSuccess,
}) => {
@@ -482,12 +479,9 @@ const prevAdjustmentModalOpenRef = useRef(false);
},
{
name: "warehouse",
label: warehouseDisplay === "slot" ? t("Slot") : t("Warehouse"),
label: t("Warehouse"),
renderCell: (params) => {
const code = params.warehouse?.code ?? "";
if (warehouseDisplay !== "slot") return code;
const parts = code.split("-").filter(Boolean);
return parts[parts.length - 1] || code;
return `${params.warehouse.code}`
},
},
{
@@ -528,7 +522,7 @@ const prevAdjustmentModalOpenRef = useRef(false);
// }
// },
],
[t, onDetailClick, downloadQrCode, handleStockTransfer, handlePrintClick, warehouseDisplay],
[t, onDetailClick, downloadQrCode, handleStockTransfer, handlePrintClick],
);



+ 30
- 123
src/components/InventorySearch/InventorySearch.tsx Vedi File

@@ -20,19 +20,10 @@ import { fetchItemsByPage } from '@/app/api/settings/item/actions';
import { useSession } from 'next-auth/react';
import { AUTH, hasAbility } from '@/authorities';
import { Button, Box } from '@mui/material';
import { WarehouseResult } from '@/app/api/warehouse';
import { fetchWarehouseListClient } from '@/app/api/warehouse/client';
import LocationFilterBar, {
emptyLocationFilter,
isLocationAll,
LocationFilterValue,
} from './LocationFilterBar';

interface Props {
inventories: InventoryResult[];
printerCombo?: PrinterCombo[];
warehouses?: WarehouseResult[];
enableLocationFilter?: boolean;
}

type SearchQuery = Partial<
@@ -71,13 +62,8 @@ const extractItemRecords = (res: unknown): ItemLookupRow[] => {
return [];
};

/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10 */
const InventorySearch: React.FC<Props> = ({
inventories,
printerCombo,
warehouses = [],
enableLocationFilter = false,
}) => {
/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.3 | 2026-09-07 */
const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
const { t } = useTranslation(['inventory', 'common', 'item']);
const { data: session } = useSession();
const abilities = session?.abilities ?? session?.user?.abilities ?? [];
@@ -153,19 +139,6 @@ const InventorySearch: React.FC<Props> = ({
// Resolved lot no for filtering
const [lotNoFilter, setLotNoFilter] = useState('');
const [scannedItemId, setScannedItemId] = useState<number | null>(null);
const [location, setLocation] = useState<LocationFilterValue>(emptyLocationFilter);
const [locationWarehouses, setLocationWarehouses] = useState<WarehouseResult[]>(warehouses);

useEffect(() => {
if (!enableLocationFilter) return;
if (warehouses.length) {
setLocationWarehouses(warehouses);
return;
}
fetchWarehouseListClient()
.then(setLocationWarehouses)
.catch(console.error);
}, [enableLocationFilter, warehouses]);

const defaultInputs = useMemo(
() => ({
@@ -212,43 +185,28 @@ const InventorySearch: React.FC<Props> = ({
);

// Inventory
const withLocationParams = useCallback(
<T extends object>(params: T, loc: LocationFilterValue): T & Partial<LocationFilterValue> => {
if (!enableLocationFilter) return params;
return {
...params,
...(!isLocationAll(loc.storeId) ? { storeId: loc.storeId } : {}),
...(!isLocationAll(loc.warehouse) ? { warehouse: loc.warehouse } : {}),
...(!isLocationAll(loc.area) ? { area: loc.area } : {}),
};
},
[enableLocationFilter],
);

const refetchInventoryData = useCallback(
async (
query: Record<SearchParamNames, string>,
actionType: 'reset' | 'search' | 'paging' | 'init',
pagingController: typeof defaultPagingController,
lotNo: string,
loc: LocationFilterValue = location,
) => {
//console.log('%c Action Type 1.', 'color:red', actionType);
// Avoid loading data again
if (actionType === 'paging' && pagingController === defaultPagingController) {
return;
}

const params: SearchInventory = withLocationParams(
{
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,
},
loc,
);
// console.log('%c Action Type 2.', 'color:blue', actionType);

const params: SearchInventory = {
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,
};

const response = await fetchInventoriesLatest(params);

@@ -262,21 +220,19 @@ const InventorySearch: React.FC<Props> = ({
break;
case 'paging':
setFilteredInventories((fi) =>
uniqBy([...fi, ...response.records], (row) => `${row.itemId}-${row.uomId ?? row.uomUdfudesc ?? ''}`),
uniqBy([...fi, ...response.records], 'itemId'),
);
}
}

return response;
},
[enableLocationFilter, location, withLocationParams],
[],
);

useEffect(() => {
refetchInventoryData(defaultInputs, 'init', defaultPagingController, '');
// Mount / tab open only. Location changes search via handleLocationChange.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultInputs]);
}, [defaultInputs, refetchInventoryData]);

useEffect(() => {
// if (!isEqual(inventoriesPagingController, defaultPagingController)) {
@@ -290,8 +246,6 @@ const InventorySearch: React.FC<Props> = ({
itemId: number | null,
actionType: 'reset' | 'search' | 'paging',
pagingController: typeof defaultPagingController,
loc: LocationFilterValue = location,
uomId?: number,
) => {
if (!itemId) {
setSelectedInventory(null);
@@ -305,15 +259,11 @@ const InventorySearch: React.FC<Props> = ({
return;
}

const params: SearchInventoryLotLine = withLocationParams(
{
itemId,
uomId: uomId || undefined,
pageNum: pagingController.pageNum - 1,
pageSize: pagingController.pageSize,
},
loc,
);
const params: SearchInventoryLotLine = {
itemId,
pageNum: pagingController.pageNum - 1,
pageSize: pagingController.pageSize,
};

const response = await fetchInventoryLotLines(params);
if (response) {
@@ -328,27 +278,20 @@ const InventorySearch: React.FC<Props> = ({
}
}
},
[location, withLocationParams],
[],
);

useEffect(() => {
// if (!isEqual(inventoryLotLinesPagingController, defaultPagingController)) {
refetchInventoryLotLineData(
selectedInventory?.itemId ?? null,
'paging',
inventoryLotLinesPagingController,
location,
selectedInventory?.uomId,
)
refetchInventoryLotLineData(selectedInventory?.itemId ?? null, 'paging', inventoryLotLinesPagingController)
// }
}, [inventoryLotLinesPagingController])

// Reset
const onReset = useCallback(() => {
const clearedLocation = emptyLocationFilter();
setLocation(clearedLocation);
refetchInventoryData(defaultInputs, 'reset', defaultPagingController, '', clearedLocation);
refetchInventoryLotLineData(null, 'reset', defaultPagingController, clearedLocation);
refetchInventoryData(defaultInputs, 'reset', defaultPagingController, '');
refetchInventoryLotLineData(null, 'reset', defaultPagingController);
// setFilteredInventories(inventories);

setLotNoFilter('');
setScannedItemId(null);
@@ -361,32 +304,14 @@ const InventorySearch: React.FC<Props> = ({
setInventoryLotLinesPagingController(() => defaultPagingController)
}, [defaultInputs, qrScanner, refetchInventoryData, refetchInventoryLotLineData]);

const handleLocationChange = useCallback(
(next: LocationFilterValue) => {
setLocation(next);
setSelectedInventory(null);
setFilteredInventoryLotLines([]);
setInventoryLotLinesTotalCount(0);
setInventoryLotLinesPagingController(() => defaultPagingController);
setInventoriesPagingController(() => defaultPagingController);
if (searchInFlightRef.current) return;
searchInFlightRef.current = true;
refetchInventoryData(inputs, 'search', defaultPagingController, lotNoFilter, next)
.finally(() => {
searchInFlightRef.current = false;
});
},
[inputs, lotNoFilter, refetchInventoryData],
);

// Click Row
const onInventoryRowClick = useCallback(
(item: InventoryResult) => {
refetchInventoryLotLineData(item.itemId, 'search', defaultPagingController, location, item.uomId);
refetchInventoryLotLineData(item.itemId, 'search', defaultPagingController);
setSelectedInventory(item);
setInventoryLotLinesPagingController(() => defaultPagingController);
},
[location, refetchInventoryLotLineData],
[refetchInventoryLotLineData],
);

// On Search
@@ -409,7 +334,7 @@ const InventorySearch: React.FC<Props> = ({
setInventoryLotLinesPagingController(() => defaultPagingController);

// No inventory rows: look up item master so 0-qty rows can be selected for stock adjustment.
if (canStockAdjust && !enableLocationFilter && invRes?.records?.length === 0) {
if (canStockAdjust && invRes?.records?.length === 0) {
try {
const items = await lookupItemsByCodeOrName(query.itemCode, query.itemName);
const typeFilter = query.itemType?.trim();
@@ -441,7 +366,6 @@ const InventorySearch: React.FC<Props> = ({
lookupItemsByCodeOrName,
applyItemsAsSyntheticInventories,
canStockAdjust,
enableLocationFilter,
],
);

@@ -527,15 +451,6 @@ const InventorySearch: React.FC<Props> = ({
onSearch(query);
}}
onReset={onReset}
extraCriteria={
enableLocationFilter ? (
<LocationFilterBar
warehouses={locationWarehouses}
value={location}
onChange={handleLocationChange}
/>
) : undefined
}
extraActions={
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}>
{scanUiMode === 'idle' ? (
@@ -569,20 +484,16 @@ const InventorySearch: React.FC<Props> = ({
totalCount={inventoryLotLinesTotalCount}
inventory={selectedInventory}
filterLotNo={lotNoFilter}
warehouseDisplay={enableLocationFilter ? "slot" : "full"}
printerCombo={printerCombo ?? []}
onStockTransferSuccess={() =>
refetchInventoryLotLineData(
selectedInventory?.itemId ?? null,
'search',
inventoryLotLinesPagingController,
location,
selectedInventory?.uomId,
)
}
onStockAdjustmentSuccess={async () => {
const itemId = selectedInventory?.itemId ?? null;
const uomId = selectedInventory?.uomId;

// Refresh both blocks:
// - middle: InventoryTable (inventories list)
@@ -598,15 +509,11 @@ const InventorySearch: React.FC<Props> = ({
itemId,
'search',
inventoryLotLinesPagingController,
location,
uomId,
);

// 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 && (uomId == null || r.uomId === uomId),
);
const target = invRes.records.find((r) => r.itemId === itemId);
if (target) setSelectedInventory(target);
}
}}


+ 0
- 54
src/components/InventorySearch/InventorySearchPage.tsx Vedi File

@@ -1,54 +0,0 @@
"use client";

import { InventoryResult } from "@/app/api/inventory";
import { PrinterCombo } from "@/app/api/settings/printer";
import { WarehouseResult } from "@/app/api/warehouse";
import { Box, Tab, Tabs } from "@mui/material";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import InventorySearch from "./InventorySearch";

type TabValue = "item" | "location";

interface Props {
inventories: InventoryResult[];
printerCombo?: PrinterCombo[];
warehouses?: WarehouseResult[];
}

/** FP-MTMS Version Checklist | Functions Ref. No. 79 | v1.0.0 | 2026-09-10 */
const InventorySearchPage: React.FC<Props> = ({
inventories,
printerCombo,
warehouses = [],
}) => {
const { t } = useTranslation("inventory");
const [tab, setTab] = useState<TabValue>("item");

const handleTabChange = useCallback((_: React.SyntheticEvent, value: string) => {
setTab(value as TabValue);
}, []);

return (
<Box>
<Tabs value={tab} onChange={handleTabChange} sx={{ mb: 2 }}>
<Tab value="item" label={t("Item Search")} />
<Tab value="location" label={t("Location Search")} />
</Tabs>

{tab === "item" && (
<InventorySearch inventories={inventories} printerCombo={printerCombo} />
)}
{tab === "location" && (
<InventorySearch
inventories={inventories}
printerCombo={printerCombo}
warehouses={warehouses}
enableLocationFilter
/>
)}
</Box>
);
};

export default InventorySearchPage;

+ 3
- 12
src/components/InventorySearch/InventorySearchWrapper.tsx Vedi File

@@ -1,29 +1,20 @@
import React from "react";
import GeneralLoading from "../General/GeneralLoading";
import { fetchInventories } from "@/app/api/inventory";
import InventorySearchPage from "./InventorySearchPage";
import InventorySearch from "./InventorySearch";
import { fetchPrinterCombo } from "@/app/api/settings/printer";
import { fetchWarehouseList } from "@/app/api/warehouse";

interface SubComponents {
Loading: typeof GeneralLoading;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 79 | v1.0.0 | 2026-09-10 */
const InventorySearchWrapper: React.FC & SubComponents = async () => {
const [inventories, printerCombo, warehouses] = await Promise.all([
const [inventories, printerCombo] = await Promise.all([
fetchInventories(),
fetchPrinterCombo(),
fetchWarehouseList().catch(() => []),
]);

return (
<InventorySearchPage
inventories={inventories}
printerCombo={printerCombo ?? []}
warehouses={warehouses ?? []}
/>
);
return <InventorySearch inventories={inventories} printerCombo={printerCombo ?? []} />;
};

InventorySearchWrapper.Loading = GeneralLoading;


+ 0
- 229
src/components/InventorySearch/LocationFilterBar.tsx Vedi File

@@ -1,229 +0,0 @@
"use client";

import { WarehouseResult } from "@/app/api/warehouse";
import { Box, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";

export const LOCATION_ALL = "ALL";
const BUTTONS_PER_ROW = 15;
const BUTTON_WIDTH = 80;

export type LocationFilterValue = {
storeId: string;
warehouse: string;
area: string;
};

const EMPTY_LOCATION: LocationFilterValue = {
storeId: "",
warehouse: "",
area: "",
};

export const emptyLocationFilter = (): LocationFilterValue => ({ ...EMPTY_LOCATION });

export const isLocationAll = (value?: string) => !value || value === LOCATION_ALL;

const warehouseSegments = (w: WarehouseResult) => {
const parts = (w.code || "").split("-");
return {
storeId: w.store_id?.trim() || parts[0] || "",
warehouse: w.warehouse?.trim() || parts[1] || "",
area: w.area?.trim() || parts[2] || "",
};
};

const compareAlphanumeric = (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });

const chunk = <T,>(items: T[], size: number): T[][] => {
const rows: T[][] = [];
for (let i = 0; i < items.length; i += size) {
rows.push(items.slice(i, i + size));
}
return rows;
};

const withAllOption = (options: string[]) =>
options.length > 1 ? [LOCATION_ALL, ...options] : options;

interface Props {
warehouses: WarehouseResult[];
value: LocationFilterValue;
onChange: (next: LocationFilterValue) => void;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 79 | v1.0.0 | 2026-09-10 */
const LocationFilterBar: React.FC<Props> = ({ warehouses, value, onChange }) => {
const { t } = useTranslation("inventory");

const floors = useMemo(() => {
const set = new Set<string>();
warehouses.forEach((w) => {
const storeId = warehouseSegments(w).storeId;
if (storeId) set.add(storeId);
});
return Array.from(set).sort(compareAlphanumeric);
}, [warehouses]);

const warehouseEnabled = Boolean(value.storeId);
const areaEnabled = Boolean(value.storeId && value.warehouse);

const warehouseZones = useMemo(() => {
if (!value.storeId) return [];
const set = new Set<string>();
warehouses.forEach((w) => {
const seg = warehouseSegments(w);
if (!isLocationAll(value.storeId) && seg.storeId !== value.storeId) return;
if (seg.warehouse) set.add(seg.warehouse);
});
return Array.from(set).sort(compareAlphanumeric);
}, [warehouses, value.storeId]);

const areas = useMemo(() => {
if (!value.storeId || !value.warehouse) return [];
const set = new Set<string>();
warehouses.forEach((w) => {
const seg = warehouseSegments(w);
if (!isLocationAll(value.storeId) && seg.storeId !== value.storeId) return;
if (!isLocationAll(value.warehouse) && seg.warehouse !== value.warehouse) return;
if (seg.area) set.add(seg.area);
});
return Array.from(set).sort(compareAlphanumeric);
}, [warehouses, value.storeId, value.warehouse]);

const floorOptions = useMemo(() => withAllOption(floors), [floors]);

const warehouseRows = useMemo(
() => chunk(withAllOption(warehouseZones), BUTTONS_PER_ROW),
[warehouseZones],
);

const areaRows = useMemo(
() => chunk(withAllOption(areas), BUTTONS_PER_ROW),
[areas],
);

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 1.5, mb: 1 }}>
<Box sx={{ display: "flex", alignItems: "center", gap: 1.5, flexWrap: "wrap" }}>
<Typography variant="body2" sx={{ minWidth: 72, fontWeight: 600 }}>
{t("Floor")}
</Typography>
<ToggleButtonGroup
exclusive
size="small"
value={value.storeId || null}
onChange={(_, next: string | null) => {
onChange({ storeId: next ?? "", warehouse: "", area: "" });
}}
>
{floorOptions.map((floor) => (
<ToggleButton key={floor} value={floor} sx={{ px: 1.5, textTransform: "none" }}>
{floor === LOCATION_ALL ? t("All") : floor}
</ToggleButton>
))}
</ToggleButtonGroup>
</Box>

<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5, flexWrap: "wrap" }}>
<Typography variant="body2" sx={{ minWidth: 72, fontWeight: 600, mt: 0.75 }}>
{t("Warehouse")}
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 0.75,
opacity: warehouseEnabled ? 1 : 0.45,
}}
>
{warehouseRows.map((row, rowIndex) => (
<ToggleButtonGroup
key={row.join("-") || rowIndex}
exclusive
size="small"
disabled={!warehouseEnabled}
value={warehouseEnabled ? value.warehouse || null : null}
onChange={(_, next: string | null) => {
if (next == null) return;
onChange({ ...value, warehouse: next, area: "" });
}}
sx={{
"& .MuiToggleButtonGroup-grouped": {
width: BUTTON_WIDTH,
minWidth: BUTTON_WIDTH,
maxWidth: BUTTON_WIDTH,
px: 0,
boxSizing: "border-box",
},
}}
>
{row.map((zone) => (
<ToggleButton key={zone} value={zone} sx={{ textTransform: "none" }}>
{zone === LOCATION_ALL ? t("All") : zone}
</ToggleButton>
))}
</ToggleButtonGroup>
))}
</Box>
{!warehouseEnabled && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.75 }}>
{t("Select floor first")}
</Typography>
)}
</Box>

<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5, flexWrap: "wrap" }}>
<Typography variant="body2" sx={{ minWidth: 72, fontWeight: 600, mt: 0.75 }}>
{t("Area")}
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 0.75,
opacity: areaEnabled ? 1 : 0.45,
}}
>
{areaRows.map((row, rowIndex) => (
<ToggleButtonGroup
key={row.join("-") || rowIndex}
exclusive
size="small"
disabled={!areaEnabled}
value={areaEnabled ? value.area || null : null}
onChange={(_, next: string | null) => {
if (next == null) return;
onChange({ ...value, area: next });
}}
sx={{
"& .MuiToggleButtonGroup-grouped": {
width: BUTTON_WIDTH,
minWidth: BUTTON_WIDTH,
maxWidth: BUTTON_WIDTH,
px: 0,
boxSizing: "border-box",
},
}}
>
{row.map((area) => (
<ToggleButton key={area} value={area} sx={{ textTransform: "none" }}>
{area === LOCATION_ALL ? t("All") : area}
</ToggleButton>
))}
</ToggleButtonGroup>
))}
</Box>
{!areaEnabled && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.75 }}>
{value.storeId ? t("Select warehouse first") : t("Select floor first")}
</Typography>
)}
</Box>
</Box>
);
};

export default LocationFilterBar;

+ 0
- 5
src/components/SearchBox/SearchBox.tsx Vedi File

@@ -124,19 +124,15 @@ interface Props<T extends string> {
onReset?: () => void;
/** Optional actions rendered in the same row as Reset/Search (e.g. Download, Upload buttons) */
extraActions?: React.ReactNode;
/** Optional filters rendered above the standard criteria fields */
extraCriteria?: React.ReactNode;
/** Disable inputs/actions while external task is running */
disabled?: boolean;
}

/** FP-MTMS Version Checklist | Functions Ref. No. 79 | v1.0.0 | 2026-09-10 */
function SearchBox<T extends string>({
criteria,
onSearch,
onReset,
extraActions,
extraCriteria,
disabled = false,
}: Props<T>) {
const { t } = useTranslation("common");
@@ -299,7 +295,6 @@ function SearchBox<T extends string>({
<Typography className="app-search-criteria-label" variant="overline" sx={{ display: "block", mb: 0.5 }}>
{t("Search Criteria")}
</Typography>
{extraCriteria}
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
{criteria.map((c) => {
return (


+ 0
- 8
src/i18n/en/inventory.json Vedi File

@@ -16,14 +16,7 @@
"Download QR Code": "Download QR Code",
"Edit mode": "Edit mode",
"Enter item code or name to search": "Enter item code or name to search",
"Area": "Area",
"Expiry Date": "Expiry Date",
"Floor": "Floor",
"Item Search": "Item Search",
"Location Search": "Location Search",
"Select a floor to search by location.": "Select a floor to search by location.",
"Select floor first": "Select a floor first",
"Select warehouse first": "Select a warehouse first",
"FG": "Finished good",
"Failed to transfer stock": "Failed to transfer stock",
"Failed to transfer stock. Please try again.": "Failed to transfer stock. Please try again.",
@@ -60,7 +53,6 @@
"Remove": "Remove",
"Reset": "Reset",
"SFG": "Semi-finished good",
"Slot": "Slot",
"Save": "Save",
"Save failed": "Save failed",
"Saved successfully": "Saved successfully",


+ 0
- 8
src/i18n/zh/inventory.json Vedi File

@@ -16,14 +16,7 @@
"Download QR Code": "下載",
"Edit mode": "編輯模式",
"Enter item code or name to search": "輸入貨品編號或名稱以搜索",
"Area": "區域",
"Expiry Date": "到期日",
"Floor": "樓層",
"Item Search": "貨品搜尋",
"Location Search": "倉位搜尋",
"Select a floor to search by location.": "請先選擇樓層以搜尋倉位。",
"Select floor first": "請先選擇樓層",
"Select warehouse first": "請先選擇倉庫",
"FG": "成品",
"Failed to transfer stock": "轉倉失敗",
"Failed to transfer stock. Please try again.": "轉倉失敗,請重試。",
@@ -60,7 +53,6 @@
"Remove": "移除",
"Reset": "重置",
"SFG": "半成品",
"Slot": "儲位",
"Save": "儲存",
"Save failed": "儲存失敗",
"Saved successfully": "儲存成功",


Caricamento…
Annulla
Salva