301 lines
9.4 KiB
TypeScript
301 lines
9.4 KiB
TypeScript
/**
|
||
* 倉庫管理主元件
|
||
* 重構後:職責更清晰,邏輯更模組化
|
||
*/
|
||
|
||
import { useState } from "react";
|
||
import { Plus, ArrowRightLeft } from "lucide-react";
|
||
import { Button } from "./ui/button";
|
||
import {
|
||
AlertDialog,
|
||
AlertDialogAction,
|
||
AlertDialogCancel,
|
||
AlertDialogContent,
|
||
AlertDialogDescription,
|
||
AlertDialogFooter,
|
||
AlertDialogHeader,
|
||
AlertDialogTitle,
|
||
} from "./ui/alert-dialog";
|
||
import { toast } from "sonner@2.0.3";
|
||
import WarehouseDialog from "./WarehouseDialog";
|
||
import TransferOrderDialog, { TransferOrder } from "./TransferOrderDialog";
|
||
import SearchToolbar from "./shared/SearchToolbar";
|
||
import WarehouseFilter from "./warehouse/WarehouseFilter";
|
||
import WarehouseCard from "./warehouse/WarehouseCard";
|
||
import WarehouseEmptyState from "./warehouse/WarehouseEmptyState";
|
||
import { Warehouse, WarehouseInventory, Store, SafetyStockSetting } from "../types/warehouse";
|
||
import { useWarehouseFilter } from "../hooks/useWarehouseFilter";
|
||
import { calculateWarehouseStats, hasWarehouseWarning } from "../utils/inventory";
|
||
import { generateId, getCurrentDate, generateOrderNumber } from "../utils/format";
|
||
|
||
export type { Warehouse, WarehouseInventory };
|
||
|
||
interface WarehouseManagementProps {
|
||
onNavigateToInventory: (warehouseId: string) => void;
|
||
inventories: WarehouseInventory[];
|
||
onUpdateInventories: (inventories: WarehouseInventory[]) => void;
|
||
safetyStockSettings: SafetyStockSetting[];
|
||
}
|
||
|
||
export default function WarehouseManagement({
|
||
onNavigateToInventory,
|
||
inventories,
|
||
onUpdateInventories,
|
||
safetyStockSettings,
|
||
}: WarehouseManagementProps) {
|
||
// 門市列表(Mock 資料)
|
||
const [stores] = useState<Store[]>([
|
||
{
|
||
id: "store-1",
|
||
name: "信義門市",
|
||
address: "台北市信義區信義路五段10號",
|
||
},
|
||
{
|
||
id: "store-2",
|
||
name: "敦化門市",
|
||
address: "台北市大安區敦化南路一段100號",
|
||
},
|
||
{
|
||
id: "store-3",
|
||
name: "南京門市",
|
||
address: "台北市松山區南京東路三段256號",
|
||
},
|
||
]);
|
||
|
||
// 倉庫狀態
|
||
const [warehouses, setWarehouses] = useState<Warehouse[]>([
|
||
{
|
||
id: "1",
|
||
name: "中央倉庫",
|
||
address: "台北市信義區信義路五段7號",
|
||
manager: "張經理",
|
||
phone: "02-1234-5678",
|
||
description: "主要原物料儲存倉庫",
|
||
createdAt: "2025-11-01",
|
||
type: "中央倉庫",
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "門市冷藏庫",
|
||
address: "台北市大安區敦化南路一段100號",
|
||
manager: "李主管",
|
||
phone: "02-8765-4321",
|
||
description: "門市專用冷藏倉庫",
|
||
createdAt: "2025-11-10",
|
||
type: "門市",
|
||
storeId: "store-2",
|
||
storeName: "敦化門市",
|
||
},
|
||
]);
|
||
|
||
// 篩選狀態
|
||
const [searchTerm, setSearchTerm] = useState("");
|
||
const [typeFilter, setTypeFilter] = useState<string>("all");
|
||
|
||
// 對話框狀態
|
||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||
const [editingWarehouse, setEditingWarehouse] = useState<Warehouse | null>(null);
|
||
const [transferOrderDialogOpen, setTransferOrderDialogOpen] = useState(false);
|
||
const [editingTransferOrder, setEditingTransferOrder] = useState<TransferOrder | null>(null);
|
||
const [transferOrders, setTransferOrders] = useState<TransferOrder[]>([]);
|
||
|
||
// 使用篩選 Hook
|
||
const filteredWarehouses = useWarehouseFilter(warehouses, searchTerm, typeFilter);
|
||
|
||
// 倉庫操作處理函式
|
||
const handleAddWarehouse = () => {
|
||
setEditingWarehouse(null);
|
||
setIsDialogOpen(true);
|
||
};
|
||
|
||
const handleEditWarehouse = (warehouse: Warehouse) => {
|
||
setEditingWarehouse(warehouse);
|
||
setIsDialogOpen(true);
|
||
};
|
||
|
||
const handleSaveWarehouse = (warehouse: Omit<Warehouse, "id" | "createdAt">) => {
|
||
if (editingWarehouse) {
|
||
setWarehouses(
|
||
warehouses.map((w) =>
|
||
w.id === editingWarehouse.id ? { ...w, ...warehouse } : w
|
||
)
|
||
);
|
||
toast.success("倉庫資料已更新");
|
||
} else {
|
||
const newWarehouse: Warehouse = {
|
||
id: generateId(),
|
||
...warehouse,
|
||
createdAt: getCurrentDate(),
|
||
};
|
||
setWarehouses([...warehouses, newWarehouse]);
|
||
toast.success("倉庫已新增");
|
||
}
|
||
setIsDialogOpen(false);
|
||
setEditingWarehouse(null);
|
||
};
|
||
|
||
const handleDeleteWarehouse = (id: string) => {
|
||
setWarehouses(warehouses.filter((w) => w.id !== id));
|
||
onUpdateInventories(
|
||
inventories.filter((inv) => inv.warehouseId !== id)
|
||
);
|
||
toast.success("倉庫已刪除");
|
||
};
|
||
|
||
// 撥補單操作處理函式
|
||
const handleAddTransferOrder = () => {
|
||
setEditingTransferOrder(null);
|
||
setTransferOrderDialogOpen(true);
|
||
};
|
||
|
||
const handleSaveTransferOrder = (
|
||
orderData: Omit<TransferOrder, "id" | "createdAt" | "orderNumber">
|
||
) => {
|
||
const newOrder: TransferOrder = {
|
||
id: generateId(),
|
||
orderNumber: generateOrderNumber(),
|
||
...orderData,
|
||
createdAt: getCurrentDate(),
|
||
};
|
||
setTransferOrders([...transferOrders, newOrder]);
|
||
|
||
// 更新庫存
|
||
const updatedInventories = [...inventories];
|
||
|
||
// 尋找來源倉庫庫存
|
||
const sourceIndex = updatedInventories.findIndex(
|
||
(inv) =>
|
||
inv.warehouseId === orderData.sourceWarehouseId &&
|
||
inv.productId === orderData.productId &&
|
||
inv.batchNumber === orderData.batchNumber
|
||
);
|
||
|
||
if (sourceIndex !== -1) {
|
||
// 減少來源倉庫數量
|
||
updatedInventories[sourceIndex] = {
|
||
...updatedInventories[sourceIndex],
|
||
quantity: updatedInventories[sourceIndex].quantity - orderData.quantity,
|
||
};
|
||
|
||
// 尋找或建立目標倉庫庫存
|
||
const targetIndex = updatedInventories.findIndex(
|
||
(inv) =>
|
||
inv.warehouseId === orderData.targetWarehouseId &&
|
||
inv.productId === orderData.productId &&
|
||
inv.batchNumber === orderData.batchNumber
|
||
);
|
||
|
||
if (targetIndex !== -1) {
|
||
// 更新既有目標倉庫數量
|
||
updatedInventories[targetIndex] = {
|
||
...updatedInventories[targetIndex],
|
||
quantity: updatedInventories[targetIndex].quantity + orderData.quantity,
|
||
};
|
||
} else {
|
||
// 建立新的目標倉庫庫存項目
|
||
const sourceItem = updatedInventories[sourceIndex];
|
||
updatedInventories.push({
|
||
warehouseId: orderData.targetWarehouseId,
|
||
productId: orderData.productId,
|
||
productName: orderData.productName,
|
||
quantity: orderData.quantity,
|
||
batchNumber: orderData.batchNumber,
|
||
expiryDate: sourceItem.expiryDate,
|
||
});
|
||
}
|
||
|
||
onUpdateInventories(updatedInventories);
|
||
}
|
||
|
||
toast.success(`撥補單 ${newOrder.orderNumber} 已新增並完成庫存調整`);
|
||
setTransferOrderDialogOpen(false);
|
||
};
|
||
|
||
return (
|
||
<div className="container mx-auto p-6 max-w-7xl">
|
||
{/* 頁面標題 */}
|
||
<div className="mb-6">
|
||
<h1 className="mb-2">倉庫管理</h1>
|
||
<p className="text-gray-600">管理倉庫資訊與庫存配置</p>
|
||
</div>
|
||
|
||
{/* 工具列 */}
|
||
<div className="bg-white rounded-lg shadow-sm border p-4 mb-6">
|
||
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center justify-between">
|
||
<div className="flex flex-col sm:flex-row gap-4 flex-1 w-full">
|
||
{/* 搜尋框 */}
|
||
<SearchToolbar
|
||
value={searchTerm}
|
||
onChange={setSearchTerm}
|
||
placeholder="搜尋倉庫名稱..."
|
||
className="flex-1 w-full md:max-w-md"
|
||
/>
|
||
|
||
{/* 類別篩選 */}
|
||
<WarehouseFilter
|
||
value={typeFilter}
|
||
onChange={setTypeFilter}
|
||
className="w-full sm:w-48"
|
||
/>
|
||
</div>
|
||
|
||
{/* 操作按鈕 */}
|
||
<div className="flex gap-2 w-full md:w-auto">
|
||
<Button
|
||
onClick={handleAddTransferOrder}
|
||
className="flex-1 md:flex-initial button-outlined-primary"
|
||
>
|
||
<Plus className="mr-2 h-4 w-4" />
|
||
新增撥補單
|
||
</Button>
|
||
<Button
|
||
onClick={handleAddWarehouse}
|
||
className="flex-1 md:flex-initial button-filled-primary"
|
||
>
|
||
<Plus className="mr-2 h-4 w-4" />
|
||
新增倉庫
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 倉庫卡片列表 */}
|
||
{filteredWarehouses.length === 0 ? (
|
||
<WarehouseEmptyState />
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{filteredWarehouses.map((warehouse) => (
|
||
<WarehouseCard
|
||
key={warehouse.id}
|
||
warehouse={warehouse}
|
||
stats={calculateWarehouseStats(inventories, warehouse.id, safetyStockSettings)}
|
||
hasWarning={hasWarehouseWarning(inventories, warehouse.id, safetyStockSettings)}
|
||
onViewInventory={onNavigateToInventory}
|
||
onEdit={handleEditWarehouse}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* 倉庫對話框 */}
|
||
<WarehouseDialog
|
||
open={isDialogOpen}
|
||
onOpenChange={setIsDialogOpen}
|
||
warehouse={editingWarehouse}
|
||
onSave={handleSaveWarehouse}
|
||
onDelete={handleDeleteWarehouse}
|
||
stores={stores}
|
||
/>
|
||
|
||
{/* 撥補單對話框 */}
|
||
<TransferOrderDialog
|
||
open={transferOrderDialogOpen}
|
||
onOpenChange={setTransferOrderDialogOpen}
|
||
order={editingTransferOrder}
|
||
onSave={handleSaveTransferOrder}
|
||
warehouses={warehouses}
|
||
inventories={inventories}
|
||
/>
|
||
</div>
|
||
);
|
||
} |