import { LayoutDashboard, ChevronRight, Package, ShoppingCart, Menu, X, PanelLeftClose, PanelLeftOpen, Boxes, Warehouse, Truck, Contact2, LogOut, User, ChevronDown, Settings, Shield, Users, FileText, Wallet, BarChart3, FileSpreadsheet, BookOpen, ClipboardCheck, ArrowLeftRight } from "lucide-react"; import { toast, Toaster } from "sonner"; import { useState, useEffect, useMemo, useRef } from "react"; import { Link, usePage, Head } from "@inertiajs/react"; import { cn } from "@/lib/utils"; import BreadcrumbNav, { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/Components/ui/dropdown-menu"; import { usePermission } from "@/hooks/usePermission"; import ApplicationLogo from "@/Components/ApplicationLogo"; import { generateLightestColor, generateLightColor, generateDarkColor, generateActiveColor } from "@/utils/colorUtils"; import { PageProps } from "@/types/global"; interface MenuItem { id: string; label: string; icon?: React.ReactNode; route?: string; children?: MenuItem[]; permission?: string | string[]; // 所需權限(單一或多個,滿足任一即可) } export default function AuthenticatedLayout({ children, breadcrumbs }: { children: React.ReactNode, breadcrumbs?: BreadcrumbItemType[] }) { const { url, props } = usePage(); const branding = props.branding; const user = props.auth?.user || { name: 'Guest', username: 'guest', roles: [], role_labels: [], permissions: [] }; const { can, canAny } = usePermission(); const [isCollapsed, setIsCollapsed] = useState(() => { if (typeof window !== "undefined") { return localStorage.getItem("sidebar-collapsed") === "true"; } return false; }); const [isMobileOpen, setIsMobileOpen] = useState(false); // 完整的菜單定義(含權限配置) const allMenuItems: MenuItem[] = [ { id: "dashboard", label: "儀表板", icon: , route: "/", // 儀表板無需特定權限,所有登入使用者皆可存取 }, { id: "inventory-management", label: "商品與庫存管理", icon: , permission: ["products.view", "warehouses.view", "inventory.view"], // 滿足任一即可看到此群組 children: [ { id: "product-management", label: "商品資料管理", icon: , route: "/products", permission: "products.view", }, { id: "warehouse-management", label: "倉庫管理", icon: , route: "/warehouses", permission: "warehouses.view", }, { id: "stock-counting", label: "庫存盤點", icon: , route: "/inventory/count-docs", permission: "inventory_count.view", }, { id: "stock-adjustment", label: "庫存盤調", icon: , route: "/inventory/adjust-docs", permission: "inventory_adjust.view", }, { id: "stock-transfer", label: "庫存調撥", icon: , route: "/inventory/transfer-orders", permission: "inventory_transfer.view", }, ], }, { id: "supply-chain-management", label: "供應鏈管理", icon: , permission: ["vendors.view", "purchase_orders.view", "goods_receipts.view"], children: [ { id: "vendor-list", label: "廠商資料管理", icon: , route: "/vendors", permission: "vendors.view", }, { id: "purchase-order-list", label: "採購單管理", icon: , route: "/purchase-orders", permission: "purchase_orders.view", }, { id: "goods-receipt-list", label: "進貨單管理", icon: , route: "/goods-receipts", permission: "goods_receipts.view", }, { id: "delivery-note-list", label: "出貨單管理 (功能製作中)", icon: , route: "/delivery-notes", permission: "delivery_notes.view", }, ], }, { id: "production-management", label: "生產管理", icon: , permission: ["production_orders.view", "recipes.view"], children: [ { id: "recipe-list", label: "配方管理", icon: , route: "/recipes", permission: "recipes.view", }, { id: "production-order-list", label: "生產工單", icon: , route: "/production-orders", permission: "production_orders.view", }, ], }, { id: "finance-management", label: "財務管理", icon: , permission: "utility_fees.view", children: [ { id: "utility-fee-list", label: "公共事業費", icon: , route: "/utility-fees", permission: "utility_fees.view", }, ], }, { id: "report-management", label: "報表管理", icon: , permission: "accounting.view", children: [ { id: "accounting-report", label: "會計報表", icon: , route: "/accounting-report", permission: "accounting.view", }, ], }, { id: "system-management", label: "系統管理", icon: , permission: ["users.view", "roles.view"], children: [ { id: "user-management", label: "使用者管理", icon: , route: "/admin/users", permission: "users.view", }, { id: "role-management", label: "角色與權限", icon: , route: "/admin/roles", permission: "roles.view", }, { id: "activity-log", label: "操作紀錄", icon: , route: "/admin/activity-logs", permission: "system.view_logs", }, ], }, ]; // 檢查單一項目是否有權限 const hasPermissionForItem = (item: MenuItem): boolean => { if (!item.permission) return true; // 無指定權限則預設有權限 if (Array.isArray(item.permission)) { return canAny(item.permission); } return can(item.permission); }; // 過濾菜單:移除無權限的項目,若父層所有子項目都無權限則隱藏父層 const menuItems = useMemo(() => { return allMenuItems .map((item) => { // 如果有子項目,先過濾子項目 if (item.children && item.children.length > 0) { const filteredChildren = item.children.filter(hasPermissionForItem); // 若所有子項目都無權限,則隱藏整個群組 if (filteredChildren.length === 0) return null; return { ...item, children: filteredChildren }; } // 無子項目的單一選單,直接檢查權限 if (!hasPermissionForItem(item)) return null; return item; }) .filter((item): item is MenuItem => item !== null); }, [can, canAny]); // 初始化狀態:優先讀取 localStorage const [expandedItems, setExpandedItems] = useState(() => { try { const saved = localStorage.getItem("sidebar-expanded-items"); if (saved) return JSON.parse(saved); } catch (e) { console.error("Failed to parse sidebar state", e); } // 如果沒有存檔,則預設僅展開當前 URL 對應的群組 const activeItem = menuItems.find(item => item.children?.some(child => child.route && url.startsWith(child.route)) ); const defaultExpanded = ["inventory-management"]; if (activeItem && !defaultExpanded.includes(activeItem.id)) { defaultExpanded.push(activeItem.id); } return defaultExpanded; }); // 監聽 URL 變化,確保「當前」頁面所屬群組是展開的 // 但不要影響其他群組的狀態(除非使用者手動切換) useEffect(() => { const activeItem = menuItems.find(item => item.children?.some(child => child.route && url.startsWith(child.route)) ); if (activeItem && !expandedItems.includes(activeItem.id)) { setExpandedItems(prev => { const next = [...prev, activeItem.id]; localStorage.setItem("sidebar-expanded-items", JSON.stringify(next)); return next; }); } }, [url]); useEffect(() => { localStorage.setItem("sidebar-collapsed", String(isCollapsed)); }, [isCollapsed]); // 全域監聽 flash 訊息並顯示 Toast const lastFlash = useRef(null); useEffect(() => { if (!props.flash) return; // 檢查是否與上次顯示的訊息相同(透過簡單的物件引用比對,Inertia 在重導向後會產生新的 props 物件) if (props.flash === lastFlash.current) return; if (props.flash.success) { toast.success(props.flash.success); } if (props.flash.error) { toast.error(props.flash.error); } lastFlash.current = props.flash; }, [props.flash]); const toggleExpand = (itemId: string) => { if (isCollapsed) { setIsCollapsed(false); if (!expandedItems.includes(itemId)) { setExpandedItems(prev => [...prev, itemId]); } return; } setExpandedItems((prev) => { const next = prev.includes(itemId) ? prev.filter((id) => id !== itemId) : [...prev, itemId]; localStorage.setItem("sidebar-expanded-items", JSON.stringify(next)); return next; }); }; const renderMenuItem = (item: MenuItem, level: number = 0) => { const hasChildren = item.children && item.children.length > 0; const isExpanded = expandedItems.includes(item.id); const isActive = item.route ? (item.route === '/' ? url === '/' : url.startsWith(item.route)) : false; return (
{hasChildren ? ( ) : ( setIsMobileOpen(false)} className={cn( "w-full flex items-center transition-all rounded-lg group", level === 0 ? "px-3 py-2.5" : "px-3 py-2", level > 0 && !isCollapsed && "pl-11", isActive ? "bg-primary-lightest text-primary-main" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900", isCollapsed && level === 0 && "justify-center px-0 h-10 w-10 mx-auto" )} title={isCollapsed ? item.label : ""} > {item.icon && ( {item.icon} )} {!isCollapsed && ( {item.label} )} )} {hasChildren && isExpanded && !isCollapsed && (
{item.children?.map((child) => renderMenuItem(child, level + 1))}
)}
); }; return (
{/* Mobile Header -> Global Header */}
{branding?.short_name || 'Star'} ERP
{/* User Menu */}
{user.name} ({user.username}) {user.role_labels?.[0] || user.roles?.[0] || '一般用戶'}
{user.name} ({user.username}) 使用者設定 登出系統
{/* Sidebar Desktop */} {/* Mobile Sidebar Overlay */} { isMobileOpen && (
setIsMobileOpen(false)} /> ) } {/* Mobile Sidebar Drawer */} {/* Main Content */}
{breadcrumbs && breadcrumbs.length > 1 && ( )}
{children}
Copyright © {new Date().getFullYear()} {branding?.name || branding?.short_name || 'Star ERP'}. All rights reserved. Design by 星科技
); }