Files
encoach_frontend_new_v3/src/components/AppLayout.tsx
Yamen Ahmad 88a385ed07 feat: initial frontend codebase — EnCoach v3
React 18 + TypeScript + Vite 5 + Tailwind CSS + shadcn/ui frontend:
- Multi-role auth: student, teacher, admin, corporate, agent, developer
- Student portal: dashboard, courses, adaptive learning, exams, placement
- Teacher portal: courses, assignments, grading, attendance, AI workbench
- Admin portal: 60+ management pages (LMS, exams, users, entities, AI)
- AI-powered features: study coach, generation page, gap analysis, TTS
- Exam system: session, auto-save, results, grading queue
- Full IELTS workflow: reading/listening/writing/speaking modules

Made-with: Cursor
2026-04-11 15:47:32 +04:00

146 lines
5.5 KiB
TypeScript

import { Outlet, useLocation, Link, useNavigate } from "react-router-dom";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/AppSidebar";
import {
Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList,
BreadcrumbPage, BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Ticket, Settings, User, LogOut, HelpCircle } from "lucide-react";
import React from "react";
import AiAssistantDrawer from "@/components/ai/AiAssistantDrawer";
import AiSearchBar from "@/components/ai/AiSearchBar";
const routeLabels: Record<string, string> = {
"dashboard": "Dashboard",
"admin": "Admin",
"assignments": "Assignments",
"examsList": "Exams List",
"exam-structures": "Exam Structures",
"rubrics": "Rubrics",
"generation": "Exam Generation",
"approval-workflows": "Approval Workflows",
"users": "Users",
"entities": "Entities",
"classrooms": "Classrooms",
"student-performance": "Student Performance",
"stats-corporate": "Stats Corporate",
"record": "Record",
"training": "Training",
"vocabulary": "Vocabulary",
"grammar": "Grammar",
"payment-record": "Payment Record",
"tickets": "Tickets",
"settings": "Settings",
"profile": "Profile",
"exam": "Exam",
"tutor": "Tutor",
};
function AppBreadcrumbs() {
const location = useLocation();
const segments = location.pathname.split("/").filter(Boolean);
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to="/dashboard/admin">Home</Link>
</BreadcrumbLink>
</BreadcrumbItem>
{segments.map((seg, i) => {
const path = "/" + segments.slice(0, i + 1).join("/");
const label = routeLabels[seg] || seg.charAt(0).toUpperCase() + seg.slice(1);
const isLast = i === segments.length - 1;
return (
<React.Fragment key={path}>
<BreadcrumbSeparator />
<BreadcrumbItem>
{isLast ? (
<BreadcrumbPage>{label}</BreadcrumbPage>
) : (
<BreadcrumbLink asChild>
<Link to={path}>{label}</Link>
</BreadcrumbLink>
)}
</BreadcrumbItem>
</React.Fragment>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
}
export default function AppLayout() {
const navigate = useNavigate();
return (
<SidebarProvider>
<div className="min-h-screen flex w-full">
<AppSidebar />
<div className="flex-1 flex flex-col min-w-0">
<header className="h-14 border-b bg-card flex items-center justify-between px-4 shrink-0">
<div className="flex items-center gap-3">
<SidebarTrigger />
<AppBreadcrumbs />
</div>
<AiSearchBar />
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={() => navigate("/tickets")} className="text-muted-foreground hover:text-foreground">
<Ticket className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={() => navigate("/settings")} className="text-muted-foreground hover:text-foreground">
<Settings className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="rounded-full">
<div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center">
<span className="text-xs font-semibold text-primary-foreground">AU</span>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<div className="px-3 py-2">
<p className="text-sm font-medium">Admin User</p>
<p className="text-xs text-muted-foreground">admin@encoach.com</p>
</div>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => navigate("/profile")}>
<User className="mr-2 h-4 w-4" /> Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate("/settings")}>
<Settings className="mr-2 h-4 w-4" /> Settings
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => navigate("/login")}>
<LogOut className="mr-2 h-4 w-4" /> Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
<main className="flex-1 overflow-auto p-6">
<Outlet />
</main>
</div>
</div>
<AiAssistantDrawer />
{/* Floating help button */}
<Link
to="/tickets"
className="fixed bottom-6 right-6 z-50 flex items-center gap-2 rounded-full bg-primary px-4 py-2.5 text-primary-foreground shadow-lg hover:opacity-90 transition-opacity"
>
<HelpCircle className="h-4 w-4" />
<span className="text-sm font-medium">Need help?</span>
</Link>
</SidebarProvider>
);
}