From bd519115617a35c8bdecad9e186c455dd237ae10 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Tue, 6 Jan 2026 16:17:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=81=B4=E9=82=8A=E6=94=94=E9=9F=BF=E6=87=89?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/DashboardController.php | 32 +++ resources/js/Layouts/AuthenticatedLayout.tsx | 195 +++++++++++++---- resources/js/Pages/Dashboard.tsx | 208 +++++++++++++++++++ routes/web.php | 6 +- 4 files changed, 397 insertions(+), 44 deletions(-) create mode 100644 app/Http/Controllers/DashboardController.php create mode 100644 resources/js/Pages/Dashboard.tsx diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php new file mode 100644 index 0000000..f6ecd02 --- /dev/null +++ b/app/Http/Controllers/DashboardController.php @@ -0,0 +1,32 @@ + Product::count(), + 'vendorsCount' => Vendor::count(), + 'purchaseOrdersCount' => PurchaseOrder::count(), + 'warehousesCount' => Warehouse::count(), + 'totalInventoryValue' => Inventory::join('products', 'inventories.product_id', '=', 'products.id') + ->sum('inventories.quantity'), // Simplified, maybe just sum quantities for now + 'pendingOrdersCount' => PurchaseOrder::where('status', 'pending')->count(), + 'lowStockCount' => Inventory::whereColumn('quantity', '<=', 'safety_stock')->count(), + ]; + + return Inertia::render('Dashboard', [ + 'stats' => $stats, + ]); + } +} diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx index 67035c0..78b19ad 100644 --- a/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/resources/js/Layouts/AuthenticatedLayout.tsx @@ -1,4 +1,4 @@ -import { ChevronDown, ChevronRight, Package, ClipboardList, ShoppingCart } from "lucide-react"; +import { ChevronDown, ChevronRight, Package, ClipboardList, ShoppingCart, Menu, X, PanelLeftClose, PanelLeftOpen } from "lucide-react"; import { Toaster } from "sonner"; import { useState, useEffect } from "react"; import { Link, usePage } from "@inertiajs/react"; @@ -14,6 +14,14 @@ interface MenuItem { export default function AuthenticatedLayout({ children }: { children: React.ReactNode }) { const { url } = usePage(); + const [isCollapsed, setIsCollapsed] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem("sidebar-collapsed") === "true"; + } + return false; + }); + const [isMobileOpen, setIsMobileOpen] = useState(false); + const menuItems: MenuItem[] = [ { id: "inventory-management", @@ -94,7 +102,19 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac } }, [url]); + useEffect(() => { + localStorage.setItem("sidebar-collapsed", String(isCollapsed)); + }, [isCollapsed]); + 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) @@ -111,56 +131,74 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac const isActive = item.route ? url.startsWith(item.route) : false; return ( -
+
{hasChildren ? ( ) : ( setIsMobileOpen(false)} className={cn( - "w-full flex items-center gap-3 px-4 py-3 transition-all rounded-md", - level === 0 && "hover:bg-background-light", - level > 0 && "hover:bg-background-light-grey pl-10", - isActive && "bg-[#33bc9a]/20 font-medium" + "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 : ""} > - {!hasChildren && level > 0 && } {item.icon && ( - + {item.icon} )} - - {item.label} - + {!isCollapsed && ( + + {item.label} + + )} )} - {hasChildren && isExpanded && ( -
+ {hasChildren && isExpanded && !isCollapsed && ( +
{item.children?.map((child) => renderMenuItem(child, level + 1))}
)} @@ -169,23 +207,98 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac }; return ( -
- {/* Sidebar 底色強制為白色 bg-white */} -