diff --git a/src/app/(main)/settings/qrCodeHandle/page.tsx b/src/app/(main)/settings/qrCodeHandle/page.tsx
index e0a84c7..d363561 100644
--- a/src/app/(main)/settings/qrCodeHandle/page.tsx
+++ b/src/app/(main)/settings/qrCodeHandle/page.tsx
@@ -4,6 +4,7 @@ import Typography from "@mui/material/Typography";
import { getServerI18n } from "@/i18n";
import QrCodeHandleSearchWrapper from "@/components/qrCodeHandles/qrCodeHandleSearchWrapper";
import QrCodeHandleEquipmentSearchWrapper from "@/components/qrCodeHandles/qrCodeHandleEquipmentSearchWrapper";
+import QrCodeHandleWarehouseSearchWrapper from "@/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper";
import QrCodeHandleTabs from "@/components/qrCodeHandles/qrCodeHandleTabs";
import { I18nProvider } from "@/i18n";
import Box from "@mui/material/Box";
@@ -19,7 +20,7 @@ const QrCodeHandlePage: React.FC = async () => {
{t("QR Code Handle")}
-
+
}>
@@ -35,6 +36,13 @@ const QrCodeHandlePage: React.FC = async () => {
}
+ warehouseTabContent={
+ }>
+
+
+
+
+ }
/>
diff --git a/src/app/api/warehouse/client.ts b/src/app/api/warehouse/client.ts
new file mode 100644
index 0000000..454d48a
--- /dev/null
+++ b/src/app/api/warehouse/client.ts
@@ -0,0 +1,33 @@
+"use client";
+
+import { NEXT_PUBLIC_API_URL } from "@/config/api";
+import { WarehouseResult } from "./index";
+
+export const exportWarehouseQrCode = async (warehouseIds: number[]): Promise<{ blobValue: Uint8Array; filename: string }> => {
+
+ const token = localStorage.getItem("accessToken");
+
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/warehouse/export-qrcode`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
+ body: JSON.stringify({ warehouseIds }),
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ throw new Error("Unauthorized: Please log in again");
+ }
+ throw new Error(`Failed to export QR code: ${response.status} ${response.statusText}`);
+ }
+
+ const filename = response.headers.get("Content-Disposition")?.split("filename=")[1]?.replace(/"/g, "") || "warehouse_qrcode.pdf";
+
+ const blob = await response.blob();
+ const arrayBuffer = await blob.arrayBuffer();
+ const blobValue = new Uint8Array(arrayBuffer);
+
+ return { blobValue, filename };
+};
diff --git a/src/components/WarehouseHandle/WarehouseHandle.tsx b/src/components/WarehouseHandle/WarehouseHandle.tsx
index 453de68..97e471b 100644
--- a/src/components/WarehouseHandle/WarehouseHandle.tsx
+++ b/src/components/WarehouseHandle/WarehouseHandle.tsx
@@ -53,8 +53,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
successDialog(t("Delete Success"), t);
} catch (error) {
console.error("Failed to delete warehouse:", error);
- // Don't redirect on error, just show error message
- // The error will be logged but user stays on the page
}
}, t);
}, [t, router]);
@@ -76,18 +74,14 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
try {
let results: WarehouseResult[] = warehouses;
- // Build search pattern from the four fields: store_idF-warehouse-area-slot
- // Only search by code field - match the code that follows this pattern
const storeId = searchInputs.store_id?.trim() || "";
const warehouse = searchInputs.warehouse?.trim() || "";
const area = searchInputs.area?.trim() || "";
const slot = searchInputs.slot?.trim() || "";
const stockTakeSection = searchInputs.stockTakeSection?.trim() || "";
- // If any field has a value, filter by code pattern and stockTakeSection
if (storeId || warehouse || area || slot || stockTakeSection) {
results = warehouses.filter((warehouseItem) => {
- // Filter by stockTakeSection if provided
if (stockTakeSection) {
const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
if (!itemStockTakeSection.includes(stockTakeSection.toLowerCase())) {
@@ -95,7 +89,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
}
}
- // Filter by code pattern if any code-related field is provided
if (storeId || warehouse || area || slot) {
if (!warehouseItem.code) {
return false;
@@ -103,8 +96,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
const codeValue = String(warehouseItem.code).toLowerCase();
- // Check if code matches the pattern: store_id-warehouse-area-slot
- // Match each part if provided
const codeParts = codeValue.split("-");
if (codeParts.length >= 4) {
@@ -121,7 +112,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
}
- // Fallback: if code doesn't follow the pattern, check if it contains any of the search terms
const storeIdMatch = !storeId || codeValue.includes(storeId.toLowerCase());
const warehouseMatch = !warehouse || codeValue.includes(warehouse.toLowerCase());
const areaMatch = !area || codeValue.includes(area.toLowerCase());
@@ -130,11 +120,9 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
}
- // If only stockTakeSection is provided, return true (already filtered above)
return true;
});
} else {
- // If no search terms, show all warehouses
results = warehouses;
}
@@ -142,7 +130,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
} catch (error) {
console.error("Error searching warehouses:", error);
- // Fallback: filter by code pattern and stockTakeSection
const storeId = searchInputs.store_id?.trim().toLowerCase() || "";
const warehouse = searchInputs.warehouse?.trim().toLowerCase() || "";
const area = searchInputs.area?.trim().toLowerCase() || "";
@@ -151,7 +138,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
setFilteredWarehouse(
warehouses.filter((warehouseItem) => {
- // Filter by stockTakeSection if provided
if (stockTakeSection) {
const itemStockTakeSection = String(warehouseItem.stockTakeSection || "").toLowerCase();
if (!itemStockTakeSection.includes(stockTakeSection)) {
@@ -159,7 +145,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
}
}
- // Filter by code if any code-related field is provided
if (storeId || warehouse || area || slot) {
if (!warehouseItem.code) {
return false;
@@ -267,7 +252,6 @@ const WarehouseHandle: React.FC = ({ warehouses }) => {
justifyContent: "flex-start",
}}
>
- {/* 樓層 field with F inside on the right */}
= ({ warehouses }) => {
-
- {/* 倉庫 field */}
= ({ warehouses }) => {
-
- {/* 區域 field */}
= ({ warehouses }) => {
-
- {/* 儲位 field */}
= ({ warehouses }) => {
size="small"
sx={{ width: "150px", minWidth: "120px" }}
/>
- {/* 盤點區域 field */}
= ({
userTabContent,
equipmentTabContent,
+ warehouseTabContent,
}) => {
const { t } = useTranslation("common");
const { t: tUser } = useTranslation("user");
+ const { t: tWarehouse } = useTranslation("warehouse");
const searchParams = useSearchParams();
const router = useRouter();
const getInitialTab = () => {
const tab = searchParams.get("tab");
if (tab === "equipment") return 1;
+ if (tab === "warehouse") return 2;
if (tab === "user") return 0;
return 0;
};
@@ -54,6 +58,8 @@ const QrCodeHandleTabs: React.FC = ({
const tab = searchParams.get("tab");
if (tab === "equipment") {
setCurrentTab(1);
+ } else if (tab === "warehouse") {
+ setCurrentTab(2);
} else if (tab === "user") {
setCurrentTab(0);
}
@@ -61,7 +67,9 @@ const QrCodeHandleTabs: React.FC = ({
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setCurrentTab(newValue);
- const tabName = newValue === 1 ? "equipment" : "user";
+ let tabName = "user";
+ if (newValue === 1) tabName = "equipment";
+ else if (newValue === 2) tabName = "warehouse";
const params = new URLSearchParams(searchParams.toString());
params.set("tab", tabName);
router.push(`?${params.toString()}`, { scroll: false });
@@ -73,6 +81,7 @@ const QrCodeHandleTabs: React.FC = ({
+
@@ -83,6 +92,10 @@ const QrCodeHandleTabs: React.FC = ({
{equipmentTabContent}
+
+
+ {warehouseTabContent}
+
);
};
diff --git a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx
new file mode 100644
index 0000000..7bed7ec
--- /dev/null
+++ b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx
@@ -0,0 +1,675 @@
+"use client";
+
+import { useCallback, useMemo, useState, useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import SearchResults, { Column } from "../SearchResults";
+import { successDialog } from "../Swal/CustomAlerts";
+import useUploadContext from "../UploadProvider/useUploadContext";
+import { downloadFile } from "@/app/utils/commonUtil";
+import { WarehouseResult } from "@/app/api/warehouse";
+import { exportWarehouseQrCode } from "@/app/api/warehouse/client";
+import {
+ Checkbox,
+ Box,
+ Button,
+ TextField,
+ Stack,
+ Autocomplete,
+ Modal,
+ Card,
+ CardContent,
+ CardActions,
+ IconButton,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Paper,
+ Typography,
+ InputAdornment
+} from "@mui/material";
+import DownloadIcon from "@mui/icons-material/Download";
+import PrintIcon from "@mui/icons-material/Print";
+import CloseIcon from "@mui/icons-material/Close";
+import RestartAlt from "@mui/icons-material/RestartAlt";
+import Search from "@mui/icons-material/Search";
+import { PrinterCombo } from "@/app/api/settings/printer";
+
+interface Props {
+ warehouses: WarehouseResult[];
+ printerCombo: PrinterCombo[];
+}
+
+const QrCodeHandleWarehouseSearch: React.FC = ({ warehouses, printerCombo }) => {
+ const { t } = useTranslation(["warehouse", "common"]);
+ const [filteredWarehouses, setFilteredWarehouses] = useState(warehouses);
+ const { setIsUploading } = useUploadContext();
+ const [pagingController, setPagingController] = useState({
+ pageNum: 1,
+ pageSize: 10,
+ });
+
+ const [checkboxIds, setCheckboxIds] = useState([]);
+ const [selectAll, setSelectAll] = useState(false);
+ const [printQty, setPrintQty] = useState(1);
+ const [isSearching, setIsSearching] = useState(false);
+
+ const [previewOpen, setPreviewOpen] = useState(false);
+ const [previewUrl, setPreviewUrl] = useState(null);
+
+ const [selectedWarehousesModalOpen, setSelectedWarehousesModalOpen] = useState(false);
+
+ const [searchInputs, setSearchInputs] = useState({
+ store_id: "",
+ warehouse: "",
+ area: "",
+ slot: "",
+ });
+
+ const filteredPrinters = useMemo(() => {
+ return printerCombo.filter((printer) => {
+ return printer.type === "A4";
+ });
+ }, [printerCombo]);
+
+ const [selectedPrinter, setSelectedPrinter] = useState(
+ filteredPrinters.length > 0 ? filteredPrinters[0] : undefined
+ );
+
+ useEffect(() => {
+ if (!selectedPrinter || !filteredPrinters.find(p => p.id === selectedPrinter.id)) {
+ setSelectedPrinter(filteredPrinters.length > 0 ? filteredPrinters[0] : undefined);
+ }
+ }, [filteredPrinters, selectedPrinter]);
+
+ const handleReset = useCallback(() => {
+ setSearchInputs({
+ store_id: "",
+ warehouse: "",
+ area: "",
+ slot: "",
+ });
+ setFilteredWarehouses(warehouses);
+ setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
+ }, [warehouses, pagingController.pageSize]);
+
+ const handleSearch = useCallback(() => {
+ setIsSearching(true);
+ try {
+ let results: WarehouseResult[] = warehouses;
+
+ const storeId = searchInputs.store_id?.trim() || "";
+ const warehouse = searchInputs.warehouse?.trim() || "";
+ const area = searchInputs.area?.trim() || "";
+ const slot = searchInputs.slot?.trim() || "";
+
+ if (storeId || warehouse || area || slot) {
+ results = warehouses.filter((warehouseItem) => {
+ if (storeId || warehouse || area || slot) {
+ if (!warehouseItem.code) {
+ return false;
+ }
+
+ const codeValue = String(warehouseItem.code).toLowerCase();
+
+ const codeParts = codeValue.split("-");
+
+ if (codeParts.length >= 4) {
+ const codeStoreId = codeParts[0] || "";
+ const codeWarehouse = codeParts[1] || "";
+ const codeArea = codeParts[2] || "";
+ const codeSlot = codeParts[3] || "";
+
+ const storeIdMatch = !storeId || codeStoreId.includes(storeId.toLowerCase());
+ const warehouseMatch = !warehouse || codeWarehouse.includes(warehouse.toLowerCase());
+ const areaMatch = !area || codeArea.includes(area.toLowerCase());
+ const slotMatch = !slot || codeSlot.includes(slot.toLowerCase());
+
+ return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
+ }
+
+ const storeIdMatch = !storeId || codeValue.includes(storeId.toLowerCase());
+ const warehouseMatch = !warehouse || codeValue.includes(warehouse.toLowerCase());
+ const areaMatch = !area || codeValue.includes(area.toLowerCase());
+ const slotMatch = !slot || codeValue.includes(slot.toLowerCase());
+
+ return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
+ }
+
+ return true;
+ });
+ } else {
+ results = warehouses;
+ }
+
+ setFilteredWarehouses(results);
+ setPagingController({ pageNum: 1, pageSize: pagingController.pageSize });
+ } catch (error) {
+ console.error("Error searching warehouses:", error);
+ const storeId = searchInputs.store_id?.trim().toLowerCase() || "";
+ const warehouse = searchInputs.warehouse?.trim().toLowerCase() || "";
+ const area = searchInputs.area?.trim().toLowerCase() || "";
+ const slot = searchInputs.slot?.trim().toLowerCase() || "";
+
+ setFilteredWarehouses(
+ warehouses.filter((warehouseItem) => {
+ if (storeId || warehouse || area || slot) {
+ if (!warehouseItem.code) {
+ return false;
+ }
+
+ const codeValue = String(warehouseItem.code).toLowerCase();
+ const codeParts = codeValue.split("-");
+
+ if (codeParts.length >= 4) {
+ const storeIdMatch = !storeId || codeParts[0].includes(storeId);
+ const warehouseMatch = !warehouse || codeParts[1].includes(warehouse);
+ const areaMatch = !area || codeParts[2].includes(area);
+ const slotMatch = !slot || codeParts[3].includes(slot);
+ return storeIdMatch && warehouseMatch && areaMatch && slotMatch;
+ }
+
+ return (!storeId || codeValue.includes(storeId)) &&
+ (!warehouse || codeValue.includes(warehouse)) &&
+ (!area || codeValue.includes(area)) &&
+ (!slot || codeValue.includes(slot));
+ }
+
+ return true;
+ })
+ );
+ } finally {
+ setIsSearching(false);
+ }
+ }, [searchInputs, warehouses, pagingController.pageSize]);
+
+ const handleSelectWarehouse = useCallback((warehouseId: number, checked: boolean) => {
+ if (checked) {
+ setCheckboxIds(prev => [...prev, warehouseId]);
+ } else {
+ setCheckboxIds(prev => prev.filter(id => id !== warehouseId));
+ setSelectAll(false);
+ }
+ }, []);
+
+ const handleSelectAll = useCallback((checked: boolean) => {
+ if (checked) {
+ setCheckboxIds(filteredWarehouses.map(warehouse => warehouse.id));
+ setSelectAll(true);
+ } else {
+ setCheckboxIds([]);
+ setSelectAll(false);
+ }
+ }, [filteredWarehouses]);
+
+ const showPdfPreview = useCallback(async (warehouseIds: number[]) => {
+ if (warehouseIds.length === 0) {
+ return;
+ }
+ try {
+ setIsUploading(true);
+ const response = await exportWarehouseQrCode(warehouseIds);
+
+ const blob = new Blob([new Uint8Array(response.blobValue)], { type: "application/pdf" });
+ const url = URL.createObjectURL(blob);
+
+ setPreviewUrl(`${url}#toolbar=0`);
+ setPreviewOpen(true);
+ } catch (error) {
+ console.error("Error exporting QR code:", error);
+ } finally {
+ setIsUploading(false);
+ }
+ }, [setIsUploading]);
+
+ const handleClosePreview = useCallback(() => {
+ setPreviewOpen(false);
+ if (previewUrl) {
+ URL.revokeObjectURL(previewUrl);
+ setPreviewUrl(null);
+ }
+ }, [previewUrl]);
+
+ const handleDownloadQrCode = useCallback(async (warehouseIds: number[]) => {
+ if (warehouseIds.length === 0) {
+ return;
+ }
+ try {
+ setIsUploading(true);
+ const response = await exportWarehouseQrCode(warehouseIds);
+ downloadFile(response.blobValue, response.filename);
+ setSelectedWarehousesModalOpen(false);
+ successDialog("二維碼已下載", t);
+ } catch (error) {
+ console.error("Error exporting QR code:", error);
+ } finally {
+ setIsUploading(false);
+ }
+ }, [setIsUploading, t]);
+
+ const handlePrint = useCallback(async () => {
+ if (checkboxIds.length === 0) {
+ return;
+ }
+ try {
+ setIsUploading(true);
+ const response = await exportWarehouseQrCode(checkboxIds);
+
+ const blob = new Blob([new Uint8Array(response.blobValue)], { type: "application/pdf" });
+ const url = URL.createObjectURL(blob);
+
+ const printWindow = window.open(url, '_blank');
+ if (printWindow) {
+ printWindow.onload = () => {
+ for (let i = 0; i < printQty; i++) {
+ setTimeout(() => {
+ printWindow.print();
+ }, i * 500);
+ }
+ };
+ }
+
+ setTimeout(() => {
+ URL.revokeObjectURL(url);
+ }, 1000);
+ setSelectedWarehousesModalOpen(false);
+ successDialog("二維碼已列印", t);
+ } catch (error) {
+ console.error("Error printing QR code:", error);
+ } finally {
+ setIsUploading(false);
+ }
+ }, [checkboxIds, printQty, setIsUploading, t]);
+
+ const handleViewSelectedQrCodes = useCallback(() => {
+ if (checkboxIds.length === 0) {
+ return;
+ }
+ setSelectedWarehousesModalOpen(true);
+ }, [checkboxIds]);
+
+ const selectedWarehouses = useMemo(() => {
+ return warehouses.filter(warehouse => checkboxIds.includes(warehouse.id));
+ }, [warehouses, checkboxIds]);
+
+ const handleCloseSelectedWarehousesModal = useCallback(() => {
+ setSelectedWarehousesModalOpen(false);
+ }, []);
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ name: "id",
+ label: "",
+ sx: { width: "50px", minWidth: "50px" },
+ renderCell: (params) => (
+ handleSelectWarehouse(params.id, e.target.checked)}
+ onClick={(e) => e.stopPropagation()}
+ />
+ ),
+ },
+ {
+ name: "code",
+ label: t("code"),
+ align: "left",
+ headerAlign: "left",
+ sx: { width: "200px", minWidth: "200px" },
+ },
+ {
+ name: "store_id",
+ label: t("store_id"),
+ align: "left",
+ headerAlign: "left",
+ sx: { width: "150px", minWidth: "150px" },
+ },
+ {
+ name: "warehouse",
+ label: t("warehouse"),
+ align: "left",
+ headerAlign: "left",
+ sx: { width: "150px", minWidth: "150px" },
+ },
+ {
+ name: "area",
+ label: t("area"),
+ align: "left",
+ headerAlign: "left",
+ sx: { width: "150px", minWidth: "150px" },
+ },
+ {
+ name: "slot",
+ label: t("slot"),
+ align: "left",
+ headerAlign: "left",
+ sx: { width: "150px", minWidth: "150px" },
+ },
+ ],
+ [t, checkboxIds, handleSelectWarehouse],
+ );
+
+ return (
+ <>
+
+
+ {t("Search Criteria")}
+
+
+ setSearchInputs((prev) => ({ ...prev, store_id: e.target.value }))
+ }
+ size="small"
+ sx={{ width: "150px", minWidth: "120px" }}
+ InputProps={{
+ endAdornment: (
+ F
+ ),
+ }}
+ />
+
+ -
+
+
+ setSearchInputs((prev) => ({ ...prev, warehouse: e.target.value }))
+ }
+ size="small"
+ sx={{ width: "150px", minWidth: "120px" }}
+ />
+
+ -
+
+
+ setSearchInputs((prev) => ({ ...prev, area: e.target.value }))
+ }
+ size="small"
+ sx={{ width: "150px", minWidth: "120px" }}
+ />
+
+ -
+
+
+ setSearchInputs((prev) => ({ ...prev, slot: e.target.value }))
+ }
+ size="small"
+ sx={{ width: "150px", minWidth: "120px" }}
+ />
+
+
+ }
+ onClick={handleReset}
+ >
+ {t("Reset")}
+
+ }
+ onClick={handleSearch}
+ >
+ {t("Search")}
+
+
+
+
+
+ items={filteredWarehouses}
+ columns={columns}
+ pagingController={pagingController}
+ setPagingController={setPagingController}
+ totalCount={filteredWarehouses.length}
+ isAutoPaging={true}
+ />
+
+
+
+
+
+
+
+
+
+ 已選擇倉庫 ({selectedWarehouses.length})
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("code")}
+
+
+ {t("store_id")}
+
+
+ {t("warehouse")}
+
+
+ {t("area")}
+
+
+ {t("slot")}
+
+
+
+
+ {selectedWarehouses.length === 0 ? (
+
+
+ 沒有選擇的倉庫
+
+
+ ) : (
+ selectedWarehouses.map((warehouse) => (
+
+ {warehouse.code || '-'}
+ {warehouse.store_id || '-'}
+ {warehouse.warehouse || '-'}
+ {warehouse.area || '-'}
+ {warehouse.slot || '-'}
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ options={filteredPrinters}
+ value={selectedPrinter ?? null}
+ onChange={(event, value) => {
+ setSelectedPrinter(value ?? undefined);
+ }}
+ getOptionLabel={(option) => option.name || option.label || option.code || String(option.id)}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ const value = parseInt(e.target.value) || 1;
+ setPrintQty(Math.max(1, value));
+ }}
+ inputProps={{ min: 1 }}
+ sx={{ width: 120 }}
+ />
+ }
+ onClick={handlePrint}
+ disabled={checkboxIds.length === 0 || filteredPrinters.length === 0}
+ color="primary"
+ >
+ 列印
+
+ }
+ onClick={() => handleDownloadQrCode(checkboxIds)}
+ disabled={checkboxIds.length === 0}
+ color="primary"
+ >
+ 下載二維碼
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {previewUrl && (
+
+ )}
+
+
+
+ >
+ );
+};
+
+export default QrCodeHandleWarehouseSearch;
diff --git a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx
new file mode 100644
index 0000000..13b8870
--- /dev/null
+++ b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import QrCodeHandleWarehouseSearch from "./qrCodeHandleWarehouseSearch";
+import QrCodeHandleSearchLoading from "./qrCodeHandleSearchLoading";
+import { fetchWarehouseList } from "@/app/api/warehouse";
+import { fetchPrinterCombo } from "@/app/api/settings/printer";
+
+interface SubComponents {
+ Loading: typeof QrCodeHandleSearchLoading;
+}
+
+const QrCodeHandleWarehouseSearchWrapper: React.FC & SubComponents = async () => {
+ const [warehouses, printerCombo] = await Promise.all([
+ fetchWarehouseList(),
+ fetchPrinterCombo(),
+ ]);
+ return ;
+};
+
+QrCodeHandleWarehouseSearchWrapper.Loading = QrCodeHandleSearchLoading;
+
+export default QrCodeHandleWarehouseSearchWrapper;