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
This commit is contained in:
145
src/components/AppLayout.tsx
Normal file
145
src/components/AppLayout.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user