188 lines
7.6 KiB
TypeScript
188 lines
7.6 KiB
TypeScript
import { useState, useMemo } from "react";
|
||
import { ArrowLeft, PackagePlus, AlertTriangle, Shield } from "lucide-react";
|
||
import { Button } from "@/Components/ui/button";
|
||
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
|
||
import { Head, Link, router } from "@inertiajs/react";
|
||
import { Warehouse, WarehouseInventory, SafetyStockSetting, Product } from "@/types/warehouse";
|
||
import InventoryToolbar from "@/Components/Warehouse/Inventory/InventoryToolbar";
|
||
import InventoryTable from "@/Components/Warehouse/Inventory/InventoryTable";
|
||
import { calculateLowStockCount } from "@/utils/inventory";
|
||
import { toast } from "sonner";
|
||
import {
|
||
AlertDialog,
|
||
AlertDialogAction,
|
||
AlertDialogCancel,
|
||
AlertDialogContent,
|
||
AlertDialogDescription,
|
||
AlertDialogFooter,
|
||
AlertDialogHeader,
|
||
AlertDialogTitle,
|
||
} from "@/Components/ui/alert-dialog";
|
||
|
||
// 庫存頁面 Props
|
||
interface Props {
|
||
warehouse: Warehouse;
|
||
inventories: WarehouseInventory[];
|
||
safetyStockSettings: SafetyStockSetting[];
|
||
availableProducts: Product[];
|
||
}
|
||
|
||
export default function WarehouseInventoryPage({
|
||
warehouse,
|
||
inventories,
|
||
safetyStockSettings,
|
||
availableProducts,
|
||
}: Props) {
|
||
const [searchTerm, setSearchTerm] = useState("");
|
||
const [typeFilter, setTypeFilter] = useState<string>("all");
|
||
const [deleteId, setDeleteId] = useState<string | null>(null);
|
||
|
||
// 篩選庫存列表
|
||
const filteredInventories = useMemo(() => {
|
||
return inventories.filter((item) => {
|
||
// 搜尋條件:匹配商品名稱、編號或批號
|
||
const matchesSearch = !searchTerm ||
|
||
item.productName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
(item.productCode && item.productCode.toLowerCase().includes(searchTerm.toLowerCase())) ||
|
||
item.batchNumber.toLowerCase().includes(searchTerm.toLowerCase());
|
||
|
||
// 類型篩選 (需要比對 availableProducts 找到類型)
|
||
let matchesType = true;
|
||
if (typeFilter !== "all") {
|
||
const product = availableProducts.find((p) => p.id === item.productId);
|
||
matchesType = product?.type === typeFilter;
|
||
}
|
||
|
||
return matchesSearch && matchesType;
|
||
});
|
||
}, [inventories, searchTerm, typeFilter, availableProducts]);
|
||
|
||
// 計算統計資訊
|
||
const lowStockItems = calculateLowStockCount(inventories, warehouse.id, safetyStockSettings);
|
||
|
||
// 導航至流動紀錄頁
|
||
const handleView = (inventoryId: string) => {
|
||
router.visit(route('warehouses.inventory.history', { warehouse: warehouse.id, inventory: inventoryId }));
|
||
};
|
||
|
||
|
||
const confirmDelete = (inventoryId: string) => {
|
||
setDeleteId(inventoryId);
|
||
};
|
||
|
||
const handleDelete = () => {
|
||
if (!deleteId) return;
|
||
|
||
router.delete(route("warehouses.inventory.destroy", { warehouse: warehouse.id, inventory: deleteId }), {
|
||
onSuccess: () => {
|
||
toast.success("庫存記錄已刪除");
|
||
setDeleteId(null);
|
||
},
|
||
onError: () => {
|
||
toast.error("刪除失敗");
|
||
}
|
||
});
|
||
};
|
||
|
||
return (
|
||
<AuthenticatedLayout>
|
||
<Head title={`庫存管理 - ${warehouse.name}`} />
|
||
<div className="container mx-auto p-6 max-w-7xl">
|
||
{/* 頁面標題與導航 */}
|
||
<div className="mb-6">
|
||
<Link href="/warehouses">
|
||
<Button
|
||
variant="outline"
|
||
className="gap-2 button-outlined-primary mb-6"
|
||
>
|
||
<ArrowLeft className="h-4 w-4" />
|
||
返回倉庫管理
|
||
</Button>
|
||
</Link>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="mb-2">庫存管理 - {warehouse.name}</h1>
|
||
<p className="text-gray-600 font-medium">查看並管理此倉庫內的商品庫存數量與批號資訊</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 操作按鈕 (位於標題下方) */}
|
||
<div className="flex items-center gap-3 mb-6">
|
||
{/* 安全庫存設定按鈕 */}
|
||
<Link href={`/warehouses/${warehouse.id}/safety-stock-settings`}>
|
||
<Button
|
||
variant="outline"
|
||
className="button-outlined-primary"
|
||
>
|
||
<Shield className="mr-2 h-4 w-4" />
|
||
安全庫存設定
|
||
</Button>
|
||
</Link>
|
||
|
||
{/* 庫存警告顯示 */}
|
||
<Button
|
||
variant="outline"
|
||
className={`button-outlined-primary cursor-default hover:bg-transparent ${lowStockItems > 0
|
||
? "border-orange-500 text-orange-600"
|
||
: "border-green-500 text-green-600"
|
||
}`}
|
||
>
|
||
<AlertTriangle className="mr-2 h-4 w-4" />
|
||
庫存警告:{lowStockItems} 項
|
||
</Button>
|
||
|
||
{/* 新增庫存按鈕 */}
|
||
<Link href={`/warehouses/${warehouse.id}/add-inventory`}>
|
||
<Button
|
||
className="button-filled-primary"
|
||
>
|
||
<PackagePlus className="mr-2 h-4 w-4" />
|
||
新增庫存
|
||
</Button>
|
||
</Link>
|
||
</div>
|
||
|
||
{/* 篩選工具列 */}
|
||
<div className="mb-6 bg-white rounded-lg shadow-sm border p-4">
|
||
<InventoryToolbar
|
||
searchTerm={searchTerm}
|
||
onSearchChange={setSearchTerm}
|
||
typeFilter={typeFilter}
|
||
onTypeFilterChange={setTypeFilter}
|
||
/>
|
||
</div>
|
||
|
||
{/* 庫存表格 */}
|
||
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
|
||
<InventoryTable
|
||
inventories={filteredInventories}
|
||
onView={handleView}
|
||
onDelete={confirmDelete}
|
||
/>
|
||
</div>
|
||
|
||
|
||
{/* 刪除確認對話框 */}
|
||
<AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
|
||
<AlertDialogContent>
|
||
<AlertDialogHeader>
|
||
<AlertDialogTitle>確認刪除庫存項目</AlertDialogTitle>
|
||
<AlertDialogDescription>
|
||
您確定要刪除此筆庫存項目嗎?此操作將會清空該項目的數量並保留刪除紀錄。此動作無法復原。
|
||
</AlertDialogDescription>
|
||
</AlertDialogHeader>
|
||
<AlertDialogFooter>
|
||
<AlertDialogCancel className="button-outlined-primary">取消</AlertDialogCancel>
|
||
<AlertDialogAction onClick={handleDelete} className="bg-red-600 hover:bg-red-700 text-white">
|
||
確認刪除
|
||
</AlertDialogAction>
|
||
</AlertDialogFooter>
|
||
</AlertDialogContent>
|
||
</AlertDialog>
|
||
</div>
|
||
</AuthenticatedLayout>
|
||
);
|
||
}
|