first commit
This commit is contained in:
301
source-code/ERP(A-b)-倉庫管理/src/components/WarehouseManagement.tsx
Normal file
301
source-code/ERP(A-b)-倉庫管理/src/components/WarehouseManagement.tsx
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* 倉庫管理主元件
|
||||
* 重構後:職責更清晰,邏輯更模組化
|
||||
*/
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user