import { Outlet, Link, useNavigate, useLocation } from "react-router-dom"; import { Suspense } from "react"; import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarHeader, SidebarFooter, SidebarSeparator, useSidebar, } from "@/components/ui/sidebar"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { NavLink } from "@/components/NavLink"; import { useAuth } from "@/contexts/AuthContext"; import NotificationDropdown from "@/components/NotificationDropdown"; import { ThemeToggle } from "@/components/ThemeToggle"; import { LanguageToggle } from "@/components/LanguageToggle"; import AiAssistantDrawer from "@/components/ai/AiAssistantDrawer"; import AiSearchBar from "@/components/ai/AiSearchBar"; import { usePermissions } from "@/hooks/usePermissions"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { GraduationCap, LogOut, User, Settings, LayoutDashboard, ClipboardList, FileText, Layers, BookOpen, Wand2, GitBranch, Users, BarChart3, Building2, History, CreditCard, Ticket, BookA, PenTool, Calendar, ChevronDown, HelpCircle, LucideIcon, Target, FolderOpen, CalendarDays, Landmark, UserPlus, ScrollText, Award, HelpCircle as FaqIcon, Bell, Workflow, CalendarOff, DollarSign, BookMarked, BarChartHorizontal, TrendingUp, Library, Activity, Warehouse, UserCog, Sparkles, Compass, } from "lucide-react"; import React from "react"; import { useTranslation } from "react-i18next"; // ============= Navigation Config ============= // Items store i18n keys (`titleKey`) rather than literal strings so Arabic // language switches update the sidebar without the component having to // re-mount. interface NavItem { titleKey: string; url: string; icon: LucideIcon } const overviewItems: NavItem[] = [ { titleKey: "nav.smartWizard", url: "/admin/smart-wizard", icon: Sparkles }, { titleKey: "nav.adminDashboard", url: "/admin/dashboard", icon: LayoutDashboard }, { titleKey: "nav.platformDashboard", url: "/admin/platform", icon: BarChart3 }, ]; const lmsItems: NavItem[] = [ { titleKey: "nav.courses", url: "/admin/courses", icon: BookOpen }, { titleKey: "nav.students", url: "/admin/students", icon: Users }, { titleKey: "nav.teachers", url: "/admin/teachers", icon: GraduationCap }, { titleKey: "nav.batches", url: "/admin/batches", icon: Layers }, { titleKey: "nav.timetable", url: "/admin/timetable", icon: Calendar }, { titleKey: "nav.reports", url: "/admin/reports", icon: BarChart3 }, ]; const academicItems: NavItem[] = [ { titleKey: "nav.assignments", url: "/admin/assignments", icon: ClipboardList }, { titleKey: "nav.examsList", url: "/admin/examsList", icon: FileText }, { titleKey: "nav.examStructures", url: "/admin/exam-structures", icon: Layers }, { titleKey: "nav.rubrics", url: "/admin/rubrics", icon: BookOpen }, { titleKey: "nav.generation", url: "/admin/generation", icon: Wand2 }, { titleKey: "nav.reviewQueue", url: "/admin/exam/review-queue", icon: Sparkles }, { titleKey: "nav.aiPrompts", url: "/admin/ai/prompts", icon: Wand2 }, { titleKey: "nav.aiFeedback", url: "/admin/ai/feedback", icon: Sparkles }, { titleKey: "nav.coursePlans", url: "/admin/course-plans", icon: Compass }, { titleKey: "nav.approvalWorkflows", url: "/admin/approval-workflows", icon: GitBranch }, ]; const adaptiveItems: NavItem[] = [ { titleKey: "nav.taxonomy", url: "/admin/taxonomy", icon: Target }, { titleKey: "nav.resources", url: "/admin/resources", icon: FolderOpen }, ]; const institutionalItems: NavItem[] = [ { titleKey: "nav.academicYears", url: "/admin/academic-years", icon: CalendarDays }, { titleKey: "nav.departments", url: "/admin/departments", icon: Landmark }, { titleKey: "nav.admissions", url: "/admin/admissions", icon: UserPlus }, { titleKey: "nav.admissionRegister", url: "/admin/admission-register", icon: ScrollText }, { titleKey: "nav.examSessions", url: "/admin/exam-sessions", icon: FileText }, { titleKey: "nav.marksheets", url: "/admin/marksheets", icon: Award }, { titleKey: "nav.studentLeave", url: "/admin/student-leave", icon: CalendarOff }, { titleKey: "nav.fees", url: "/admin/fees", icon: DollarSign }, { titleKey: "nav.lessons", url: "/admin/lessons", icon: BookMarked }, { titleKey: "nav.gradebook", url: "/admin/gradebook", icon: BarChartHorizontal }, { titleKey: "nav.studentProgress", url: "/admin/student-progress", icon: TrendingUp }, { titleKey: "nav.library", url: "/admin/library", icon: Library }, { titleKey: "nav.activities", url: "/admin/activities", icon: Activity }, { titleKey: "nav.facilities", url: "/admin/facilities", icon: Warehouse }, ]; const managementItems: NavItem[] = [ { titleKey: "nav.users", url: "/admin/users", icon: Users }, { titleKey: "nav.entities", url: "/admin/entities", icon: Building2 }, { titleKey: "nav.classrooms", url: "/admin/classrooms", icon: GraduationCap }, { titleKey: "nav.userRoles", url: "/admin/user-roles", icon: UserCog }, { titleKey: "nav.rolesPermissions", url: "/admin/roles-permissions", icon: Settings }, { titleKey: "nav.authorityMatrix", url: "/admin/authority-matrix", icon: Workflow }, ]; const reportItems: NavItem[] = [ { titleKey: "nav.studentPerformance", url: "/admin/student-performance", icon: BarChart3 }, { titleKey: "nav.statsCorporate", url: "/admin/stats-corporate", icon: Building2 }, { titleKey: "nav.record", url: "/admin/record", icon: History }, ]; const trainingItems: NavItem[] = [ { titleKey: "nav.vocabulary", url: "/admin/training/vocabulary", icon: BookA }, { titleKey: "nav.grammar", url: "/admin/training/grammar", icon: PenTool }, ]; const configItems: NavItem[] = [ { titleKey: "nav.faqManager", url: "/admin/faq", icon: FaqIcon }, { titleKey: "nav.notificationRules", url: "/admin/notification-rules", icon: Bell }, { titleKey: "nav.approvalConfig", url: "/admin/approval-config", icon: Workflow }, ]; const supportItems: NavItem[] = [ { titleKey: "nav.paymentRecord", url: "/admin/payment-record", icon: CreditCard }, { titleKey: "nav.tickets", url: "/admin/tickets", icon: Ticket }, { titleKey: "nav.settings", url: "/admin/settings-platform", icon: Settings }, ]; // ============= Reusable sidebar group ============= function SidebarNavGroup({ labelKey, items }: { labelKey: string; items: NavItem[] }) { const { state } = useSidebar(); const { t } = useTranslation(); const collapsed = state === "collapsed"; return ( {t(labelKey)} {items.map((item) => { const label = t(item.titleKey); return ( {!collapsed && {label}} ); })} ); } // ============= Breadcrumbs ============= // Map URL segments to i18n keys so breadcrumbs follow the active locale. // Segments without an entry fall back to a Title-cased version of the slug. const routeLabelKeys: Record = { admin: "breadcrumb.admin", dashboard: "breadcrumb.dashboard", platform: "breadcrumb.platform", courses: "nav.courses", students: "nav.students", teachers: "nav.teachers", batches: "nav.batches", timetable: "nav.timetable", reports: "nav.reports", assignments: "nav.assignments", examsList: "nav.examsList", "exam-structures": "nav.examStructures", rubrics: "nav.rubrics", generation: "nav.generation", "review-queue": "nav.reviewQueue", review: "breadcrumb.review", prompts: "nav.aiPrompts", ai: "breadcrumb.ai", feedback: "breadcrumb.feedback", "approval-workflows": "nav.approvalWorkflows", users: "nav.users", entities: "nav.entities", classrooms: "nav.classrooms", "student-performance": "nav.studentPerformance", "stats-corporate": "nav.statsCorporate", record: "nav.record", training: "sidebarGroup.training", vocabulary: "nav.vocabulary", grammar: "nav.grammar", "payment-record": "nav.paymentRecord", tickets: "nav.tickets", "settings-platform": "nav.settings", settings: "nav.settings", profile: "nav.profile", exam: "breadcrumb.exam", "academic-years": "nav.academicYears", departments: "nav.departments", admissions: "nav.admissions", "admission-register": "nav.admissionRegister", "exam-sessions": "nav.examSessions", marksheets: "nav.marksheets", faq: "nav.faqManager", "notification-rules": "nav.notificationRules", "approval-config": "nav.approvalConfig", "student-leave": "nav.studentLeave", fees: "nav.fees", lessons: "nav.lessons", gradebook: "nav.gradebook", "student-progress": "nav.studentProgress", library: "nav.library", activities: "nav.activities", facilities: "nav.facilities", }; function AppBreadcrumbs() { const location = useLocation(); const { t } = useTranslation(); const segments = location.pathname.split("/").filter(Boolean); return ( {t("common.home")} {segments.map((seg, i) => { const path = "/" + segments.slice(0, i + 1).join("/"); const key = routeLabelKeys[seg]; const label = key ? t(key) : seg.charAt(0).toUpperCase() + seg.slice(1); const isLast = i === segments.length - 1; return ( {isLast ? {label} : {label}} ); })} ); } // ============= Route content fallback ============= // Shown only inside the main content area while a lazy-loaded route chunk // is fetching. Keeping the fallback local means the sidebar and header // stay mounted during navigation — previously the page felt like a full // browser reload because the outer App-level Suspense replaced everything // with a full-viewport spinner. function RouteContentFallback() { return ( ); } // ============= Main Layout ============= export default function AdminLmsLayout() { const { user, logout } = useAuth(); const navigate = useNavigate(); const { t } = useTranslation(); const initials = user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??"; const handleLogout = async () => { await logout(); navigate("/login"); }; return ( navigate("/admin/tickets")} className="text-muted-foreground hover:text-foreground"> {initials} {user?.name ?? t("userMenu.userFallback")} {user?.email ?? ""} navigate("/admin/profile")}> {t("userMenu.profile")} navigate("/admin/settings-platform")}> {t("userMenu.settings")} {t("userMenu.logout")} }> {t("chrome.needHelp")} ); } // ============= Admin Sidebar ============= function AdminSidebar() { const { state } = useSidebar(); const collapsed = state === "collapsed"; const { user } = useAuth(); const { hasAnyPermission, isAdmin } = usePermissions(); const { t, i18n } = useTranslation(); const sidebarSide = i18n.dir() === "rtl" ? "right" : "left"; const showManagement = isAdmin || hasAnyPermission(["view_entities", "view_students", "view_teachers"]); const showReports = isAdmin || hasAnyPermission(["view_statistics", "view_student_performance", "view_entity_statistics"]); const showPayments = isAdmin || hasAnyPermission(["view_payment_record", "pay_entity"]); return ( {collapsed ? ( ) : ( )} {showManagement && } {showReports && } {t("sidebarGroup.training")} {!collapsed && } {trainingItems.map((item) => { const label = t(item.titleKey); return ( {!collapsed && {label}} ); })} i.url !== "/admin/payment-record")} /> {user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??"} {!collapsed && ( {user?.name ?? t("userMenu.userFallback")} {user?.email ?? ""} )} ); }
{user?.name ?? t("userMenu.userFallback")}
{user?.email ?? ""}