Files
star-erp/source-code/ERP(A-b)-倉庫管理/src/components/WarehouseManagement.tsx
2025-12-30 15:03:19 +08:00

301 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 倉庫管理主元件
* 重構後:職責更清晰,邏輯更模組化
*/
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>
);
}