diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx index ff54cb4..8b75a01 100644 --- a/src/components/AppSidebar.tsx +++ b/src/components/AppSidebar.tsx @@ -1,5 +1,4 @@ -import { useState } from "react"; -import { Link, useLocation } from "react-router-dom"; +import { Link } from "react-router-dom"; import { useAuth } from "@/contexts/AuthContext"; import { Sidebar, @@ -10,312 +9,16 @@ import { SidebarMenu, SidebarMenuItem, SidebarMenuButton, - SidebarMenuSub, - SidebarMenuSubItem, - SidebarMenuSubButton, useSidebar, } from "@/components/ui/sidebar"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; -import { - Home, - Plus, - Wallet, - List, - MessageSquare, - Star, - BookOpen, - Heart, - FileText, - User, - Users, - Settings, - LogOut, - CreditCard, - BarChart3, - MapPin, - DollarSign, - AlertTriangle, - Car, - Target, - Zap, - Eye, - Megaphone, - ChevronDown, - ChevronRight, - Hotel, - UtensilsCrossed, - Brain, - DoorOpen, - BellRing, - Key, - Receipt, - ChefHat, - Grid3x3, - Package, - Globe, - Compass, - Map, - Shield, - Radio, - Sparkles, - Leaf, - Store, - Navigation, - QrCode, -} from "lucide-react"; +import { LogOut } from "lucide-react"; +import { menuGroups } from "@/config/sidebar"; +import { SidebarMenuRenderer } from "@/components/sidebar/SidebarMenuRenderer"; export function AppSidebar() { const { open, openMobile, isMobile } = useSidebar(); const collapsed = !open && !isMobile; - const location = useLocation(); const { logout } = useAuth(); - const [expandedItems, setExpandedItems] = useState>({}); - - const toggleExpanded = (itemPath: string) => { - setExpandedItems((prev) => ({ - ...prev, - [itemPath]: !prev[itemPath], - })); - }; - - const menuItems = [ - { icon: Home, label: "Dashboard", path: "/dashboard" }, - { - icon: Settings, - label: "Admin Panel", - path: "/dashboard/admin", - subItems: [ - { icon: BarChart3, label: "Resumen General", path: "/dashboard/admin?tab=overview" }, - { icon: Users, label: "Usuarios", path: "/dashboard/admin?tab=users" }, - { icon: MapPin, label: "Proveedores", path: "/dashboard/admin?tab=services" }, - { icon: DollarSign, label: "Financiero", path: "/dashboard/admin?tab=financial" }, - { - icon: FileText, - label: "Contenido", - path: "/dashboard/admin?tab=content", - subItems: [ - { icon: Globe, label: "Destinos", path: "/dashboard/admin?tab=content-destinations" }, - { icon: MapPin, label: "Lugares", path: "/dashboard/admin?tab=content-places" }, - { icon: BookOpen, label: "Guías", path: "/dashboard/admin?tab=content-guides" }, - { icon: Car, label: "Taxis", path: "/dashboard/admin?tab=content-taxis" }, - { icon: Navigation, label: "Geolocalización", path: "/dashboard/admin?tab=content-geolocation" }, - { icon: Megaphone, label: "Promocional", path: "/dashboard/admin?tab=content-promotional" }, - { icon: Sparkles, label: "AI Guías", path: "/dashboard/admin?tab=content-ai-guides" }, - { icon: Eye, label: "AR Content", path: "/dashboard/admin?tab=content-ar" }, - ], - }, - { - icon: Map, - label: "Geolocalización", - path: "/dashboard/admin?tab=geofences", - subItems: [ - { icon: Target, label: "Geofences", path: "/dashboard/admin?tab=geofences" }, - { icon: BarChart3, label: "Analytics", path: "/dashboard/admin?tab=analytics" }, - { icon: Zap, label: "Testing", path: "/dashboard/admin?tab=testing" }, - { icon: Shield, label: "Emergencia Geo", path: "/dashboard/admin?tab=emergency-geo" }, - { icon: Compass, label: "Navegación", path: "/dashboard/admin?tab=navigation" }, - ], - }, - { icon: AlertTriangle, label: "Emergencias", path: "/dashboard/admin?tab=emergency" }, - { icon: MessageSquare, label: "Soporte", path: "/dashboard/admin?tab=support" }, - { icon: Settings, label: "Configuración", path: "/dashboard/admin?tab=config" }, - ], - }, - { - icon: Plus, - label: "Channel Manager", - path: "/dashboard/channel-manager", - subItems: [ - { icon: BarChart3, label: "Resumen", path: "/dashboard/channel-manager?tab=overview" }, - { icon: Zap, label: "Canales", path: "/dashboard/channel-manager?tab=channels" }, - { icon: Home, label: "Propiedades", path: "/dashboard/channel-manager?tab=listings" }, - { icon: BookOpen, label: "Reservas", path: "/dashboard/channel-manager?tab=reservations" }, - { icon: BarChart3, label: "Analytics", path: "/dashboard/channel-manager?tab=analytics" }, - ], - }, - { - icon: Hotel, - label: "Hotel Management", - path: "/dashboard/hotel", - subItems: [ - { icon: Home, label: "Habitaciones", path: "/dashboard/hotel/rooms" }, - { icon: DoorOpen, label: "Check-in", path: "/dashboard/hotel/checkin" }, - { icon: BellRing, label: "Room Service", path: "/dashboard/hotel/room-service" }, - { icon: Key, label: "Acceso", path: "/dashboard/hotel/keyless" }, - { icon: Users, label: "Personal", path: "/dashboard/hotel/staff" }, - ], - }, - { - icon: UtensilsCrossed, - label: "Restaurant POS", - path: "/dashboard/restaurant", - subItems: [ - { icon: CreditCard, label: "POS Terminal", path: "/dashboard/restaurant/pos" }, - { icon: Receipt, label: "Pedidos", path: "/dashboard/restaurant/orders" }, - { icon: ChefHat, label: "Cocina", path: "/dashboard/restaurant/kitchen" }, - { icon: Receipt, label: "Cuentas", path: "/dashboard/restaurant/bills" }, - { icon: Grid3x3, label: "Mesas", path: "/dashboard/restaurant/tables" }, - { icon: UtensilsCrossed, label: "Menú", path: "/dashboard/restaurant/menu" }, - { icon: Package, label: "Inventario", path: "/dashboard/restaurant/inventory" }, - { icon: Users, label: "Personal", path: "/dashboard/restaurant/staff" }, - ], - }, - { icon: Brain, label: "Personalization", path: "/dashboard/personalization" }, - { icon: Shield, label: "Security", path: "/dashboard/security" }, - { icon: Car, label: "Vehicle Management", path: "/dashboard/vehicle-management" }, - { icon: Leaf, label: "Sustainability", path: "/dashboard/sustainability" }, - { icon: Store, label: "Comercios", path: "/dashboard/establishments" }, - { icon: Wallet, label: "Wallet", path: "/dashboard/wallet" }, - { icon: MessageSquare, label: "Message", path: "/dashboard/messages", badge: "2" }, - ]; - - const listingItems = [ - { icon: List, label: "My Listing", path: "/dashboard/my-listings" }, - { icon: Star, label: "Reviews", path: "/dashboard/reviews" }, - { icon: BookOpen, label: "Bookings", path: "/dashboard/bookings" }, - { icon: Heart, label: "Bookmark", path: "/dashboard/bookmarks" }, - { icon: FileText, label: "Invoice", path: "/dashboard/invoices" }, - ]; - - const accountItems = [ - { icon: User, label: "Edit Profile", path: "/dashboard/profile" }, - { icon: CreditCard, label: "Wallet", path: "/dashboard/wallet" }, - { icon: Settings, label: "Setting", path: "/dashboard/settings" }, - ]; - - const isActive = (path: string) => { - if (path.includes("?tab=")) { - const [basePath, tabQuery] = path.split("?tab="); - return ( - location.pathname === basePath && - location.search.includes(tabQuery) - ); - } - return location.pathname === path || location.pathname.startsWith(path + "/"); - }; - - const hasActiveChild = (items: any[]) => { - return items.some((item) => { - if (isActive(item.path)) return true; - if (item.subItems) return hasActiveChild(item.subItems); - return false; - }); - }; - - const renderMenuItem = (item: any, level = 0) => { - const Icon = item.icon; - const active = isActive(item.path); - const hasSubItems = item.subItems && item.subItems.length > 0; - const isExpanded = expandedItems[item.path] || hasActiveChild(item.subItems || []); - - if (hasSubItems) { - return ( - toggleExpanded(item.path)} - > - - - - - {!collapsed && {item.label}} - {!collapsed && ( - - )} - - - - - {item.subItems.map((subItem: any) => - renderSubMenuItem(subItem, level + 1) - )} - - - - - ); - } - - return ( - - - - - {!collapsed && {item.label}} - {!collapsed && item.badge && ( - - {item.badge} - - )} - - - - ); - }; - - const renderSubMenuItem = (item: any, level: number) => { - const Icon = item.icon; - const active = isActive(item.path); - const hasSubItems = item.subItems && item.subItems.length > 0; - const isExpanded = expandedItems[item.path] || hasActiveChild(item.subItems || []); - - if (hasSubItems) { - return ( - toggleExpanded(item.path)} - > - - - - - {item.label} - - - - - - {item.subItems.map((subItem: any) => - renderSubMenuItem(subItem, level + 1) - )} - - - - - ); - } - - return ( - - - - - {item.label} - - - - ); - }; return ( @@ -331,28 +34,20 @@ export function AppSidebar() { - {/* Main Menu */} - - {!collapsed && Main Menu} - - {menuItems.map((item) => renderMenuItem(item))} - - + {/* Menu Groups */} + {menuGroups.map((group) => ( + + {!collapsed && {group.label}} + + + + + ))} - {/* Listing */} + {/* Logout */} - {!collapsed && Listing} - - {listingItems.map((item) => renderMenuItem(item))} - - - - {/* Account */} - - {!collapsed && Account} - {accountItems.map((item) => renderMenuItem(item))} diff --git a/src/components/sidebar/SidebarMenuRenderer.tsx b/src/components/sidebar/SidebarMenuRenderer.tsx new file mode 100644 index 0000000..1029908 --- /dev/null +++ b/src/components/sidebar/SidebarMenuRenderer.tsx @@ -0,0 +1,185 @@ +import { Link, useLocation } from "react-router-dom"; +import { + SidebarMenu, + SidebarMenuItem, + SidebarMenuButton, + SidebarMenuSub, + SidebarMenuSubItem, + SidebarMenuSubButton, + useSidebar, +} from "@/components/ui/sidebar"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { ChevronRight } from "lucide-react"; +import { MenuItem } from "@/config/sidebar"; +import { useState, useEffect } from "react"; + +interface SidebarMenuRendererProps { + items: MenuItem[]; + level?: number; +} + +export function SidebarMenuRenderer({ items, level = 0 }: SidebarMenuRendererProps) { + const { open, isMobile } = useSidebar(); + const collapsed = !open && !isMobile; + const location = useLocation(); + const [expandedItems, setExpandedItems] = useState>({}); + + const isActive = (path: string) => { + if (path.includes("?tab=")) { + const [basePath, tabQuery] = path.split("?tab="); + return ( + location.pathname === basePath && + location.search.includes(tabQuery) + ); + } + return location.pathname === path || location.pathname.startsWith(path + "/"); + }; + + const hasActiveChild = (items: MenuItem[]): boolean => { + return items.some((item) => { + if (isActive(item.path)) return true; + if (item.subItems) return hasActiveChild(item.subItems); + return false; + }); + }; + + const toggleExpanded = (itemPath: string) => { + setExpandedItems((prev) => ({ + ...prev, + [itemPath]: !prev[itemPath], + })); + }; + + // Auto-expand groups with active children + useEffect(() => { + items.forEach((item) => { + if (item.subItems && hasActiveChild(item.subItems)) { + setExpandedItems((prev) => ({ + ...prev, + [item.path]: true, + })); + } + }); + }, [location.pathname, location.search]); + + const renderMenuItem = (item: MenuItem) => { + const Icon = item.icon; + const active = isActive(item.path); + const hasSubItems = item.subItems && item.subItems.length > 0; + const isExpanded = expandedItems[item.path] || (hasSubItems && hasActiveChild(item.subItems)); + + if (hasSubItems) { + return ( + toggleExpanded(item.path)} + > + + + + + {!collapsed && {item.label}} + {!collapsed && ( + + )} + + + + + {item.subItems!.map((subItem) => + renderSubMenuItem(subItem) + )} + + + + + ); + } + + return ( + + + + + {!collapsed && {item.label}} + {!collapsed && item.badge && ( + + {item.badge} + + )} + + + + ); + }; + + const renderSubMenuItem = (item: MenuItem): JSX.Element => { + const Icon = item.icon; + const active = isActive(item.path); + const hasSubItems = item.subItems && item.subItems.length > 0; + const isExpanded = expandedItems[item.path] || (hasSubItems && hasActiveChild(item.subItems)); + + if (hasSubItems) { + return ( + toggleExpanded(item.path)} + > + + + + + {item.label} + + + + + + {item.subItems!.map((subItem) => + renderSubMenuItem(subItem) + )} + + + + + ); + } + + return ( + + + + + {item.label} + + + + ); + }; + + return ( + + {items.map((item) => renderMenuItem(item))} + + ); +} diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts new file mode 100644 index 0000000..21a0171 --- /dev/null +++ b/src/config/sidebar.ts @@ -0,0 +1,170 @@ +import { + Home, + Plus, + Wallet, + List, + MessageSquare, + Star, + BookOpen, + Heart, + FileText, + User, + Users, + Settings, + LogOut, + CreditCard, + BarChart3, + MapPin, + DollarSign, + AlertTriangle, + Car, + Target, + Zap, + Eye, + Megaphone, + Hotel, + UtensilsCrossed, + Brain, + DoorOpen, + BellRing, + Key, + Receipt, + ChefHat, + Grid3x3, + Package, + Globe, + Compass, + Map, + Shield, + Sparkles, + Leaf, + Store, + Navigation, +} from "lucide-react"; + +export interface MenuItem { + icon: any; + label: string; + path: string; + badge?: string; + subItems?: MenuItem[]; +} + +export interface MenuGroup { + label: string; + items: MenuItem[]; +} + +export const menuGroups: MenuGroup[] = [ + { + label: "Main Menu", + items: [ + { icon: Home, label: "Dashboard", path: "/dashboard" }, + { + icon: Settings, + label: "Admin Panel", + path: "/dashboard/admin", + subItems: [ + { icon: BarChart3, label: "Resumen General", path: "/dashboard/admin?tab=overview" }, + { icon: Users, label: "Usuarios", path: "/dashboard/admin?tab=users" }, + { icon: MapPin, label: "Proveedores", path: "/dashboard/admin?tab=services" }, + { icon: DollarSign, label: "Financiero", path: "/dashboard/admin?tab=financial" }, + { + icon: FileText, + label: "Contenido", + path: "/dashboard/admin?tab=content", + subItems: [ + { icon: Globe, label: "Destinos", path: "/dashboard/admin?tab=content-destinations" }, + { icon: MapPin, label: "Lugares", path: "/dashboard/admin?tab=content-places" }, + { icon: BookOpen, label: "Guías", path: "/dashboard/admin?tab=content-guides" }, + { icon: Car, label: "Taxis", path: "/dashboard/admin?tab=content-taxis" }, + { icon: Navigation, label: "Geolocalización", path: "/dashboard/admin?tab=content-geolocation" }, + { icon: Megaphone, label: "Promocional", path: "/dashboard/admin?tab=content-promotional" }, + { icon: Sparkles, label: "AI Guías", path: "/dashboard/admin?tab=content-ai-guides" }, + { icon: Eye, label: "AR Content", path: "/dashboard/admin?tab=content-ar" }, + ], + }, + { + icon: Map, + label: "Geolocalización", + path: "/dashboard/admin?tab=geofences", + subItems: [ + { icon: Target, label: "Geofences", path: "/dashboard/admin?tab=geofences" }, + { icon: BarChart3, label: "Analytics", path: "/dashboard/admin?tab=analytics" }, + { icon: Zap, label: "Testing", path: "/dashboard/admin?tab=testing" }, + { icon: Shield, label: "Emergencia Geo", path: "/dashboard/admin?tab=emergency-geo" }, + { icon: Compass, label: "Navegación", path: "/dashboard/admin?tab=navigation" }, + ], + }, + { icon: AlertTriangle, label: "Emergencias", path: "/dashboard/admin?tab=emergency" }, + { icon: MessageSquare, label: "Soporte", path: "/dashboard/admin?tab=support" }, + { icon: Settings, label: "Configuración", path: "/dashboard/admin?tab=config" }, + ], + }, + { + icon: Plus, + label: "Channel Manager", + path: "/dashboard/channel-manager", + subItems: [ + { icon: BarChart3, label: "Resumen", path: "/dashboard/channel-manager?tab=overview" }, + { icon: Zap, label: "Canales", path: "/dashboard/channel-manager?tab=channels" }, + { icon: Home, label: "Propiedades", path: "/dashboard/channel-manager?tab=listings" }, + { icon: BookOpen, label: "Reservas", path: "/dashboard/channel-manager?tab=reservations" }, + { icon: BarChart3, label: "Analytics", path: "/dashboard/channel-manager?tab=analytics" }, + ], + }, + { + icon: Hotel, + label: "Hotel Management", + path: "/dashboard/hotel", + subItems: [ + { icon: Home, label: "Habitaciones", path: "/dashboard/hotel/rooms" }, + { icon: DoorOpen, label: "Check-in", path: "/dashboard/hotel/checkin" }, + { icon: BellRing, label: "Room Service", path: "/dashboard/hotel/room-service" }, + { icon: Key, label: "Acceso", path: "/dashboard/hotel/keyless" }, + { icon: Users, label: "Personal", path: "/dashboard/hotel/staff" }, + ], + }, + { + icon: UtensilsCrossed, + label: "Restaurant POS", + path: "/dashboard/restaurant", + subItems: [ + { icon: CreditCard, label: "POS Terminal", path: "/dashboard/restaurant/pos" }, + { icon: Receipt, label: "Pedidos", path: "/dashboard/restaurant/orders" }, + { icon: ChefHat, label: "Cocina", path: "/dashboard/restaurant/kitchen" }, + { icon: Receipt, label: "Cuentas", path: "/dashboard/restaurant/bills" }, + { icon: Grid3x3, label: "Mesas", path: "/dashboard/restaurant/tables" }, + { icon: UtensilsCrossed, label: "Menú", path: "/dashboard/restaurant/menu" }, + { icon: Package, label: "Inventario", path: "/dashboard/restaurant/inventory" }, + { icon: Users, label: "Personal", path: "/dashboard/restaurant/staff" }, + ], + }, + { icon: Brain, label: "Personalization", path: "/dashboard/personalization" }, + { icon: Shield, label: "Security", path: "/dashboard/security" }, + { icon: Car, label: "Vehicle Management", path: "/dashboard/vehicle-management" }, + { icon: Leaf, label: "Sustainability", path: "/dashboard/sustainability" }, + { icon: Store, label: "Comercios", path: "/dashboard/establishments" }, + { icon: Wallet, label: "Wallet", path: "/dashboard/wallet" }, + { icon: MessageSquare, label: "Message", path: "/dashboard/messages", badge: "2" }, + ], + }, + { + label: "Listing", + items: [ + { icon: List, label: "My Listing", path: "/dashboard/my-listings" }, + { icon: Star, label: "Reviews", path: "/dashboard/reviews" }, + { icon: BookOpen, label: "Bookings", path: "/dashboard/bookings" }, + { icon: Heart, label: "Bookmark", path: "/dashboard/bookmarks" }, + { icon: FileText, label: "Invoice", path: "/dashboard/invoices" }, + ], + }, + { + label: "Account", + items: [ + { icon: User, label: "Edit Profile", path: "/dashboard/profile" }, + { icon: CreditCard, label: "Wallet", path: "/dashboard/wallet" }, + { icon: Settings, label: "Setting", path: "/dashboard/settings" }, + ], + }, +];