import { useState } from "react"; import { Head, router } from "@inertiajs/react"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Search, Package, AlertTriangle, MinusCircle, Clock, Download, ArrowUpDown, ArrowUp, ArrowDown, } from "lucide-react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/Components/ui/table"; import { Badge } from "@/Components/ui/badge"; import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { SearchableSelect } from "@/Components/ui/searchable-select"; import Pagination from "@/Components/shared/Pagination"; interface InventoryItem { id: number; product_code: string; product_name: string; category_name: string | null; warehouse_name: string; batch_number: string | null; quantity: number; safety_stock: number | null; expiry_date: string | null; quality_status: string | null; last_inbound: string | null; last_outbound: string | null; statuses: string[]; location: string | null; } interface PaginationLink { url: string | null; label: string; active: boolean; } interface Props { filters: { warehouse_id?: string; category_id?: string; search?: string; status?: string; sort_by?: string; sort_order?: string; per_page?: string; }; summary: { totalItems: number; lowStockCount: number; negativeCount: number; expiringCount: number; }; inventories: { data: InventoryItem[]; total: number; per_page: number; current_page: number; last_page: number; links: PaginationLink[]; }; warehouses: { id: number; name: string }[]; categories: { id: number; name: string }[]; } // 狀態 Badge const statusConfig: Record< string, { label: string; className: string } > = { normal: { label: "正常", className: "bg-green-100 text-green-800 border-green-200", }, negative: { label: "負庫存", className: "bg-red-100 text-red-800 border-red-200", }, low_stock: { label: "低庫存", className: "bg-amber-100 text-amber-800 border-amber-200", }, expiring: { label: "即將過期", className: "bg-yellow-100 text-yellow-800 border-yellow-200", }, expired: { label: "已過期", className: "bg-red-100 text-red-800 border-red-200", }, }; // 狀態篩選選項 const statusOptions = [ { label: "全部狀態", value: "" }, { label: "低庫存", value: "low_stock" }, { label: "負庫存", value: "negative" }, { label: "即將過期", value: "expiring" }, { label: "已過期", value: "expired" }, { label: "所有異常", value: "abnormal" }, ]; export default function StockQueryIndex({ filters, summary, inventories, warehouses, categories, }: Props) { const [search, setSearch] = useState(filters.search || ""); const [perPage, setPerPage] = useState( filters.per_page || "10" ); // 執行篩選 const applyFilters = (newFilters: Record) => { const merged = { ...filters, ...newFilters, page: undefined }; // 移除空值 const cleaned: Record = {}; Object.entries(merged).forEach(([key, value]) => { if (value !== undefined && value !== "" && value !== null) { cleaned[key] = String(value); } }); router.get(route("inventory.stock-query.index"), cleaned, { preserveState: true, replace: true, }); }; // 搜尋 const handleSearch = () => { applyFilters({ search: search || undefined }); }; const handleSearchKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { handleSearch(); } }; // 排序 const handleSort = (field: string) => { let newSortBy: string | undefined = field; let newSortOrder: string | undefined = "asc"; if (filters.sort_by === field) { if (filters.sort_order === "asc") { newSortOrder = "desc"; } else { newSortBy = undefined; newSortOrder = undefined; } } applyFilters({ sort_by: newSortBy, sort_order: newSortOrder }); }; // 排序圖標 const SortIcon = ({ field }: { field: string }) => { if (filters.sort_by !== field) { return ( ); } if (filters.sort_order === "asc") { return ; } return ; }; // 每頁筆數變更 const handlePerPageChange = (value: string) => { setPerPage(value); router.get( route("inventory.stock-query.index"), { ...filters, per_page: value, page: undefined }, { preserveState: false, replace: true, preserveScroll: true } ); }; // 匯出 const handleExport = () => { const params = new URLSearchParams(); if (filters.warehouse_id) params.append("warehouse_id", filters.warehouse_id); if (filters.category_id) params.append("category_id", filters.category_id); if (filters.search) params.append("search", filters.search); if (filters.status) params.append("status", filters.status); window.location.href = route("inventory.stock-query.export") + "?" + params.toString(); }; // 計算序號起始值 const startIndex = (inventories.current_page - 1) * inventories.per_page + 1; // 統計卡片 const cards = [ { label: "庫存品項", value: summary.totalItems, icon: , color: "text-primary-main", bgColor: "bg-primary-lightest", borderColor: "border-primary-light", status: "", }, { label: "低庫存", value: summary.lowStockCount, icon: , color: "text-amber-600", bgColor: "bg-amber-50", borderColor: "border-amber-200", status: "low_stock", alert: summary.lowStockCount > 0, }, { label: "負庫存", value: summary.negativeCount, icon: , color: "text-red-600", bgColor: "bg-red-50", borderColor: "border-red-200", status: "negative", alert: summary.negativeCount > 0, }, { label: "即將過期", value: summary.expiringCount, icon: , color: "text-yellow-600", bgColor: "bg-yellow-50", borderColor: "border-yellow-200", status: "expiring", alert: summary.expiringCount > 0, }, ]; return (
{/* 頁面標題 */}

即時庫存查詢

跨倉庫即時庫存查詢,含批號追蹤與到期管理

{/* 統計卡片 */}
{cards.map((card) => (
applyFilters({ status: card.status || undefined, }) } className={`relative rounded-xl border ${card.borderColor} ${card.bgColor} p-4 transition-all hover:shadow-md hover:-translate-y-0.5 cursor-pointer ${filters.status === card.status ? "ring-2 ring-primary-main ring-offset-1" : "" }`} > {card.alert && ( )}
{card.icon}
{card.label}
{card.value.toLocaleString()}
))}
{/* 篩選列 */}
applyFilters({ warehouse_id: v || undefined, }) } options={[ { label: "全部倉庫", value: "" }, ...warehouses.map((w) => ({ label: w.name, value: String(w.id), })), ]} className="w-[160px] h-9" placeholder="選擇倉庫" /> applyFilters({ category_id: v || undefined, }) } options={[ { label: "全部分類", value: "" }, ...categories.map((c) => ({ label: c.name, value: String(c.id), })), ]} className="w-[160px] h-9" placeholder="選擇分類" /> applyFilters({ status: v || undefined }) } options={statusOptions} className="w-[140px] h-9" showSearch={false} placeholder="篩選狀態" />
setSearch(e.target.value)} onKeyDown={handleSearchKeyDown} placeholder="搜尋商品代碼或名稱..." className="h-9" />
{/* 庫存明細表格 */}
# 分類 批號 儲位/貨道 安全庫存 狀態 最後入庫 最後出庫 {inventories.data.length === 0 ? ( 無符合條件的資料 ) : ( inventories.data.map((item: InventoryItem, index) => ( {startIndex + index} {item.product_code} {item.product_name} {item.category_name || "—"} {item.warehouse_name} {item.batch_number || "—"} {item.location || "—"} {item.quantity} {item.safety_stock !== null ? item.safety_stock : "—"} {item.expiry_date || "—"}
{item.statuses.map( (status) => { const config = statusConfig[ status ]; if (!config) return null; return ( {config.label} ); } )}
{item.last_inbound || "—"} {item.last_outbound || "—"}
)) )}
{/* 分頁 */}
每頁顯示
共 {inventories.total} 筆紀錄
); }