import { useState } from "react"; import NavigationSidebar from "./components/NavigationSidebar"; import WarehouseManagement from "./components/WarehouseManagement"; import WarehouseInventoryPage from "./components/WarehouseInventoryPage"; import AddInventoryPage from "./components/AddInventoryPage"; import EditInventoryPage from "./components/EditInventoryPage"; import SafetyStockPage from "./components/SafetyStockPage"; import { Toaster } from "./components/ui/sonner"; import { WarehouseInventory, Warehouse, InventoryTransaction, InboundItem, InboundReason, SafetyStockSetting } from "./types/warehouse"; import { toast } from "sonner@2.0.3"; import { generateId } from "./utils/format"; interface PageState { page: string; params?: { warehouseId?: string; inventoryId?: string; }; } export default function App() { const [currentPage, setCurrentPage] = useState({ page: "warehouse-management", }); // Shared inventory state const [inventories, setInventories] = useState([ // ===== 中央倉(id=1)- 原物料庫存 ===== { warehouseId: "1", productId: "101", productName: "二砂糖", quantity: 50, batchNumber: "SG20251201", expiryDate: "2026-12-01", lastInboundDate: "2025-12-01", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "101", productName: "二砂糖", quantity: 45, batchNumber: "SG20251115", expiryDate: "2026-11-15", lastInboundDate: "2025-11-15", lastOutboundDate: "2025-12-08", }, { warehouseId: "1", productId: "102", productName: "黑糖", quantity: 30, batchNumber: "BS20251205", expiryDate: "2026-06-05", lastInboundDate: "2025-12-05", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "103", productName: "冰糖", quantity: 25, batchNumber: "RS20251203", expiryDate: "2026-12-03", lastInboundDate: "2025-12-03", lastOutboundDate: "2025-12-07", }, { warehouseId: "1", productId: "104", productName: "奶精粉", quantity: 18, batchNumber: "CR20251208", expiryDate: "2026-03-08", lastInboundDate: "2025-12-08", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "105", productName: "鮮奶油", quantity: 12, batchNumber: "FC20251210", expiryDate: "2025-12-25", lastInboundDate: "2025-12-10", lastOutboundDate: "2025-12-11", }, { warehouseId: "1", productId: "106", productName: "紅豆(生)", quantity: 40, batchNumber: "RBR20251202", expiryDate: "2026-06-02", lastInboundDate: "2025-12-02", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "107", productName: "綠豆(生)", quantity: 35, batchNumber: "GBR20251204", expiryDate: "2026-06-04", lastInboundDate: "2025-12-04", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "108", productName: "芋頭(生)", quantity: 28, batchNumber: "TR20251206", expiryDate: "2026-01-06", lastInboundDate: "2025-12-06", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "109", productName: "地瓜(生)", quantity: 32, batchNumber: "SPR20251207", expiryDate: "2026-01-07", lastInboundDate: "2025-12-07", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "110", productName: "仙草乾", quantity: 20, batchNumber: "GD20251205", expiryDate: "2026-12-05", lastInboundDate: "2025-12-05", lastOutboundDate: "2025-12-08", }, // ===== 中央倉(id=1)- 半成品庫存 ===== { warehouseId: "1", productId: "1", productName: "粉粿原漿", quantity: 25, batchNumber: "PG20251201", expiryDate: "2025-12-15", lastInboundDate: "2025-12-01", lastOutboundDate: "2025-12-08", }, { warehouseId: "1", productId: "1", productName: "粉粿原漿", quantity: 30, batchNumber: "PG20251128", expiryDate: "2025-12-12", lastInboundDate: "2025-11-28", lastOutboundDate: "2025-12-06", }, { warehouseId: "1", productId: "2", productName: "黑糖粉圓(未加糖)", quantity: 8, batchNumber: "BT20251203", expiryDate: "2025-12-10", lastInboundDate: "2025-12-03", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "2", productName: "黑糖粉圓(未加糖)", quantity: 15, batchNumber: "BT20251130", expiryDate: "2025-12-07", lastInboundDate: "2025-11-30", lastOutboundDate: "2025-12-05", }, { warehouseId: "1", productId: "3", productName: "熟紅豆(未加糖)", quantity: 12, batchNumber: "RB20251205", expiryDate: "2025-12-12", lastInboundDate: "2025-12-05", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "3", productName: "熟紅豆(未加糖)", quantity: 18, batchNumber: "RB20251202", expiryDate: "2025-12-09", lastInboundDate: "2025-12-02", lastOutboundDate: "2025-12-07", }, { warehouseId: "1", productId: "4", productName: "熟綠豆(未加糖)", quantity: 10, batchNumber: "GB20251204", expiryDate: "2025-12-11", lastInboundDate: "2025-12-04", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "4", productName: "熟綠豆(未加糖)", quantity: 14, batchNumber: "GB20251201", expiryDate: "2025-12-08", lastInboundDate: "2025-12-01", lastOutboundDate: "2025-12-06", }, { warehouseId: "1", productId: "5", productName: "芋頭泥(無調味)", quantity: 20, batchNumber: "TM20251206", expiryDate: "2025-12-20", lastInboundDate: "2025-12-06", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "5", productName: "芋頭泥(無調味)", quantity: 25, batchNumber: "TM20251203", expiryDate: "2025-12-17", lastInboundDate: "2025-12-03", lastOutboundDate: "2025-12-08", }, { warehouseId: "1", productId: "6", productName: "地瓜泥(無調味)", quantity: 18, batchNumber: "SM20251207", expiryDate: "2025-12-21", lastInboundDate: "2025-12-07", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "6", productName: "地瓜泥(無調味)", quantity: 22, batchNumber: "SM20251204", expiryDate: "2025-12-18", lastInboundDate: "2025-12-04", lastOutboundDate: "2025-12-09", }, { warehouseId: "1", productId: "7", productName: "仙草原凍(整塊)", quantity: 16, batchNumber: "GJ20251208", expiryDate: "2025-12-22", lastInboundDate: "2025-12-08", lastOutboundDate: "2025-12-10", }, { warehouseId: "1", productId: "7", productName: "仙草原凍(整塊)", quantity: 20, batchNumber: "GJ20251205", expiryDate: "2025-12-19", lastInboundDate: "2025-12-05", lastOutboundDate: "2025-12-09", }, // ===== 門市冷藏庫(id=2)- 半成品庫存 ===== { warehouseId: "2", productId: "1", productName: "粉粿原漿", quantity: 8, batchNumber: "PG20251209", expiryDate: "2025-12-16", lastInboundDate: "2025-12-09", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "2", productName: "黑糖粉圓(未加糖)", quantity: 6, batchNumber: "BT20251208", expiryDate: "2025-12-13", lastInboundDate: "2025-12-08", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "3", productName: "熟紅豆(未加糖)", quantity: 5, batchNumber: "RB20251209", expiryDate: "2025-12-14", lastInboundDate: "2025-12-09", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "4", productName: "熟綠豆(未加糖)", quantity: 4, batchNumber: "GB20251210", expiryDate: "2025-12-15", lastInboundDate: "2025-12-10", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "5", productName: "芋頭泥(無調味)", quantity: 10, batchNumber: "TM20251210", expiryDate: "2025-12-24", lastInboundDate: "2025-12-10", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "6", productName: "地瓜泥(無調味)", quantity: 9, batchNumber: "SM20251209", expiryDate: "2025-12-23", lastInboundDate: "2025-12-09", lastOutboundDate: "2025-12-11", }, { warehouseId: "2", productId: "7", productName: "仙草原凍(整塊)", quantity: 7, batchNumber: "GJ20251210", expiryDate: "2025-12-24", lastInboundDate: "2025-12-10", lastOutboundDate: "2025-12-11", }, ]); const [warehouses] = useState([ { id: "1", name: "中央倉", address: "台北市信義區信義路五段7號", manager: "張經理", phone: "02-1234-5678", description: "主要原物料儲存倉庫", createdAt: "2025-11-01", type: "中央倉庫" as const, }, { id: "2", name: "門市冷藏庫", address: "台北市大安區敦化南路一段100號", manager: "李主管", phone: "02-8765-4321", description: "門市專用冷藏倉庫", createdAt: "2025-11-10", type: "門市" as const, }, ]); // Shared inventory transactions state const [transactions, setTransactions] = useState([]); // Safety stock settings state const [safetyStockSettings, setSafetyStockSettings] = useState([]); const handleNavigate = (path: string, params?: { warehouseId?: string }) => { setCurrentPage({ page: path, params }); }; const handleNavigateToInventory = (warehouseId: string) => { setCurrentPage({ page: "warehouse-inventory", params: { warehouseId } }); }; const handleBackFromInventory = () => { setCurrentPage({ page: "warehouse-management" }); }; const handleSaveInventory = ( warehouseId: string, updatedInventories: WarehouseInventory[] ) => { // Remove old inventories for this warehouse const otherInventories = inventories.filter( (inv) => inv.warehouseId !== warehouseId ); // Add updated inventories setInventories([...otherInventories, ...updatedInventories]); }; const handleNavigateToEditInventory = (warehouseId: string, inventoryId: string) => { setCurrentPage({ page: "edit-inventory", params: { warehouseId, inventoryId } }); }; const handleBackFromEditInventory = (warehouseId: string) => { setCurrentPage({ page: "warehouse-inventory", params: { warehouseId } }); }; const handleUpdateInventory = ( warehouseId: string, updatedInventory: WarehouseInventory & { inventoryId: string } ) => { // 從 inventoryId 解析出索引 const inventoryIdParts = updatedInventory.inventoryId.split('-'); const index = parseInt(inventoryIdParts[inventoryIdParts.length - 1], 10); // 找出該倉庫的所有庫存及其索引 const warehouseInventories = inventories.filter((inv) => inv.warehouseId === warehouseId); const otherInventories = inventories.filter((inv) => inv.warehouseId !== warehouseId); // 更新特定索引的庫存項目 const updatedWarehouseInventories = warehouseInventories.map((inv, idx) => { return idx === index ? updatedInventory : inv; }); setInventories([...otherInventories, ...updatedWarehouseInventories]); handleBackFromEditInventory(warehouseId); }; const handleDeleteInventory = (warehouseId: string, inventoryId: string) => { // 從 inventoryId 解析出索引 const inventoryIdParts = inventoryId.split('-'); const index = parseInt(inventoryIdParts[inventoryIdParts.length - 1], 10); // 找出該倉庫的所有庫存 const warehouseInventories = inventories.filter((inv) => inv.warehouseId === warehouseId); const otherInventories = inventories.filter((inv) => inv.warehouseId !== warehouseId); // 刪除特定索引的庫存項目 const updatedWarehouseInventories = warehouseInventories.filter((_, idx) => idx !== index); setInventories([...otherInventories, ...updatedWarehouseInventories]); handleBackFromEditInventory(warehouseId); }; const handleNavigateToAddInventory = (warehouseId: string) => { setCurrentPage({ page: "add-inventory", params: { warehouseId } }); }; const handleBackFromAddInventory = (warehouseId: string) => { setCurrentPage({ page: "warehouse-inventory", params: { warehouseId } }); }; const handleNavigateToSafetyStock = (warehouseId: string) => { setCurrentPage({ page: "safety-stock", params: { warehouseId } }); }; const handleBackFromSafetyStock = (warehouseId: string) => { setCurrentPage({ page: "warehouse-inventory", params: { warehouseId } }); }; const handleSaveSafetyStockSettings = ( warehouseId: string, settings: SafetyStockSetting[] ) => { // 移除該倉庫舊的設定 const otherSettings = safetyStockSettings.filter( (s) => s.warehouseId !== warehouseId ); // 添加新的設定 setSafetyStockSettings([...otherSettings, ...settings]); }; const handleSaveInbound = ( warehouseId: string, data: { inboundDate: string; reason: InboundReason; notes: string; items: InboundItem[]; } ) => { const now = new Date().toISOString(); const warehouse = warehouses.find((w) => w.id === warehouseId); const warehouseName = warehouse?.name || ""; const currentInventories = inventories.filter((inv) => inv.warehouseId === warehouseId); const updatedInventories = [...currentInventories]; // 處理每一筆入庫明細 data.items.forEach((inboundItem) => { // 檢查是否已存在相同商品和批號的庫存 const existingIndex = updatedInventories.findIndex( (item) => item.productId === inboundItem.productId && item.batchNumber === inboundItem.batchNumber ); if (existingIndex !== -1) { // 更新既有庫存數量 updatedInventories[existingIndex] = { ...updatedInventories[existingIndex], quantity: updatedInventories[existingIndex].quantity + inboundItem.quantity, lastInboundDate: data.inboundDate, }; } else { // 新增庫存項目 const newItem: WarehouseInventory = { warehouseId, productId: inboundItem.productId, productName: inboundItem.productName, quantity: inboundItem.quantity, batchNumber: inboundItem.batchNumber, expiryDate: inboundItem.expiryDate, lastInboundDate: data.inboundDate, }; updatedInventories.push(newItem); } // 建立庫存異動記錄 const transaction: InventoryTransaction = { id: generateId(), warehouseId, warehouseName, productId: inboundItem.productId, productName: inboundItem.productName, batchNumber: inboundItem.batchNumber, quantity: inboundItem.quantity, transactionType: "手動入庫", reason: data.reason, notes: data.notes, expiryDate: inboundItem.expiryDate, operatorName: "系統使用者", createdAt: now, }; setTransactions([...transactions, transaction]); }); // 更新庫存 handleSaveInventory(warehouseId, updatedInventories); const totalInboundQty = data.items.reduce((sum, item) => sum + item.quantity, 0); toast.success(`入庫成功!共 ${data.items.length} 項商品,總數量 ${totalInboundQty}`); // 返回庫存管理頁面 handleBackFromAddInventory(warehouseId); }; const renderPage = () => { switch (currentPage.page) { case "warehouse-management": return ( ); case "warehouse-inventory": { const warehouseId = currentPage.params?.warehouseId; const warehouse = warehouses.find((w) => w.id === warehouseId); if (!warehouse) { // Fallback to warehouse management if warehouse not found return ( ); } return ( inv.warehouseId === warehouse.id )} safetyStockSettings={safetyStockSettings.filter( (s) => s.warehouseId === warehouse.id )} onBack={handleBackFromInventory} onNavigateToAddInventory={() => handleNavigateToAddInventory(warehouse.id)} onNavigateToEditInventory={(inventoryId) => handleNavigateToEditInventory(warehouse.id, inventoryId) } onNavigateToSafetyStock={() => handleNavigateToSafetyStock(warehouse.id)} /> ); } case "add-inventory": { const warehouseId = currentPage.params?.warehouseId; const warehouse = warehouses.find((w) => w.id === warehouseId); if (!warehouse) { return ( ); } return ( handleBackFromAddInventory(warehouse.id)} onSave={(data) => handleSaveInbound(warehouse.id, data)} /> ); } case "edit-inventory": { const warehouseId = currentPage.params?.warehouseId; const inventoryId = currentPage.params?.inventoryId; const warehouse = warehouses.find((w) => w.id === warehouseId); if (!warehouse || !inventoryId) { return ( ); } // 找出對應的庫存項目 const warehouseInventories = inventories.filter((inv) => inv.warehouseId === warehouseId); const inventoryIdParts = inventoryId.split('-'); const index = parseInt(inventoryIdParts[inventoryIdParts.length - 1], 10); const inventory = warehouseInventories[index]; if (!inventory) { return ( ); } return ( handleBackFromEditInventory(warehouse.id)} onSave={(updatedInventory) => handleUpdateInventory(warehouse.id, updatedInventory)} onDelete={(invId) => handleDeleteInventory(warehouse.id, invId)} /> ); } case "safety-stock": { const warehouseId = currentPage.params?.warehouseId; const warehouse = warehouses.find((w) => w.id === warehouseId); if (!warehouse) { return ( ); } // 找出該倉庫的所有庫存 const warehouseInventories = inventories.filter((inv) => inv.warehouseId === warehouseId); return ( s.warehouseId === warehouseId)} inventories={warehouseInventories} onBack={() => handleBackFromSafetyStock(warehouse.id)} onSave={(settings) => handleSaveSafetyStockSettings(warehouse.id, settings)} /> ); } default: // Default to warehouse management return ( ); } }; return (
{/* Sidebar Navigation */} {/* Main Content */}
{renderPage()}
); }