This repository has been archived on 2026-04-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
KLC-Admin-Panel-Frontend-Fi…/src/components/layout/AuthenticatedLayout.tsx
priyanshuvish bf42daef0b first commit
2025-09-03 17:02:24 +05:30

372 lines
10 KiB
TypeScript

import React, { useState } from 'react';
import { Button } from '../ui/button';
import {
Home,
FileText,
GraduationCap,
Users,
Calendar,
Globe,
BarChart3,
Settings,
Menu,
ChevronLeft,
LogOut,
User,
Shield,
Building,
UserPlus,
Webcam,
ClipboardList,
Target
} from 'lucide-react';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '../ui/breadcrumb';
import { Avatar, AvatarFallback } from '../ui/avatar';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
import klcLogoDark from '../../assets/klc-logo-white.png';
import { useNavigate } from 'react-router-dom';
interface NavigationItem {
id: string;
label: string;
icon: React.ComponentType<{ className?: string }>;
route: string;
children?: NavigationItem[];
}
interface AuthenticatedLayoutProps {
children: React.ReactNode;
currentRoute: string;
// onNavigate: (route: string) => void;
onLogout: () => void;
user: {
name: string;
email: string;
role: string;
avatar?: string;
lastLogin: string;
};
breadcrumbs?: Array<{ label: string; route?: string }>;
}
const navigationItems: NavigationItem[] = [
{
id: 'dashboard',
label: 'Dashboard',
icon: Home,
route: '/dashboard'
},
{
id: 'content',
label: 'Content',
icon: FileText,
route: '/content'
},
{
id: 'courses',
label: 'Courses',
icon: GraduationCap,
route: '/courses'
},
{
id: 'profilers',
label: 'Profilers',
icon: Target,
route: '/profilers'
},
{
id: 'programmes',
label: 'Programmes',
icon: ClipboardList,
route: '/programmes'
},
{
id: 'open-programme',
label: 'Open Programme',
icon: Globe,
route: '/open-programme'
},
{
id: 'webinars',
label: 'Webinars',
icon: Webcam,
route: '/webinars'
},
{
id: 'class-scheduler',
label: 'Class Scheduler',
icon: Calendar,
route: '/class-scheduler'
},
{
id: 'landing-pages',
label: 'Landing Pages',
icon: Globe,
route: '/landing-pages'
},
{
id: 'users',
label: 'Users',
icon: Users,
route: '/users/individual',
children: [
{
id: 'individual',
label: 'Individual Learners',
icon: User,
route: '/users/individual'
},
{
id: 'organizations',
label: 'Organizations',
icon: Building,
route: '/users/organizations'
}
]
},
{
id: 'roles',
label: 'Roles',
icon: Shield,
route: '/admin/roles'
},
{
id: 'analytics',
label: 'Analytics',
icon: BarChart3,
route: '/admin/analytics'
},
{
id: 'settings',
label: 'Settings',
icon: Settings,
route: '/settings',
children: [
{
id: 'leads',
label: 'Leads',
icon: UserPlus,
route: '/admin/leads'
},
{
id: 'facilities',
label: 'Facilities',
icon: Building,
route: '/admin/facilities'
}
]
}
];
export function AuthenticatedLayout({
children,
currentRoute,
// onNavigate,
onLogout,
user,
breadcrumbs = []
}: AuthenticatedLayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const isActiveRoute = (route: string) => {
return currentRoute === route ||
(route === '/users/individual' && currentRoute.startsWith('/users/'));
};
const getActiveParent = (items: NavigationItem[]): string | null => {
for (const item of items) {
if (isActiveRoute(item.route)) return item.id;
if (item.children) {
for (const child of item.children) {
if (isActiveRoute(child.route)) return item.id;
}
}
}
return null;
};
const activeParent = getActiveParent(navigationItems);
const renderNavigationItem = (item: NavigationItem, isChild = false) => {
const isActive = isActiveRoute(item.route);
const isParentActive = activeParent === item.id;
const Icon = item.icon;
const navigate = useNavigate();
return (
<div key={item.id}>
<Button
variant="ghost"
className={`w-full justify-start min-h-[44px] ${
isActive
? 'bg-primary text-primary-foreground'
: isParentActive && !isChild
? 'bg-accent text-accent-foreground'
: 'hover:bg-accent hover:text-accent-foreground'
} ${isChild ? 'ml-4 text-sm' : ''}`}
onClick={() => navigate(item.route)}
>
<Icon className={`${sidebarCollapsed ? 'mx-auto' : 'mr-2'} h-4 w-4 flex-shrink-0`} />
{!sidebarCollapsed && (
<span className="truncate">{item.label}</span>
)}
</Button>
{/* Render children if parent is active and not collapsed */}
{item.children && isParentActive && !sidebarCollapsed && (
<div className="mt-1 space-y-1">
{item.children.map(child => renderNavigationItem(child, true))}
</div>
)}
</div>
);
};
const navigate = useNavigate();
return (
<div className="flex h-screen bg-background">
{/* Left Sidebar */}
<div
className={`${sidebarCollapsed ? 'w-[72px]' : 'w-[240px]'} bg-sidebar border-r border-sidebar-border flex flex-col transition-all duration-200`}
role="navigation"
aria-label="Main navigation"
>
{/* Logo Header */}
<div className="p-4 border-b border-sidebar-border" style={{ backgroundColor: 'var(--color-brand-primary)' }}>
<div className="flex items-center justify-between">
{!sidebarCollapsed && (
<img
src={klcLogoDark}
alt="Kautilya Leadership Centre"
className="h-8"
/>
)}
<Button
variant="ghost"
size="sm"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="text-white hover:bg-white/10 min-h-[44px] min-w-[44px]"
aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{sidebarCollapsed ? <Menu className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
</Button>
</div>
</div>
{/* Navigation Items */}
<div className="flex-1 overflow-y-auto p-4">
<nav className="space-y-2" role="navigation">
{navigationItems.map(item => renderNavigationItem(item))}
</nav>
</div>
{/* User Section */}
<div className="p-4 border-t border-sidebar-border"
onClick={() => navigate('/profile')}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className={`w-full ${sidebarCollapsed ? 'px-2' : 'justify-start'} min-h-[44px]`}
>
<Avatar className="h-8 w-8">
<AvatarFallback style={{ backgroundColor: 'var(--color-brand-accent)', color: 'var(--color-brand-primary)' }}>
{user.name.split(' ').map(n => n[0]).join('')}
</AvatarFallback>
</Avatar>
{!sidebarCollapsed && (
<div className="ml-2 text-left">
<div className="font-medium truncate">{user.name}</div>
<div className="text-xs text-muted-foreground">{user.role}</div>
</div>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<div className="px-2 py-1.5 text-sm">
<div className="font-medium">{user.name}</div>
<div className="text-muted-foreground">{user.email}</div>
</div>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => navigate('/profile')}>
<User className="mr-2 h-4 w-4" />
Profile
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onLogout} className="text-destructive">
<LogOut className="mr-2 h-4 w-4" />
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Breadcrumb */}
{breadcrumbs.length > 0 && (
<div className="border-b bg-muted/30 px-6 py-3">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
onClick={() => navigate('/dashboard')}
className="cursor-pointer"
>
Admin
</BreadcrumbLink>
</BreadcrumbItem>
{breadcrumbs.map((crumb, index) => (
<React.Fragment key={index}>
<BreadcrumbSeparator />
<BreadcrumbItem>
{crumb.route && index < breadcrumbs.length - 1 ? (
<BreadcrumbLink
onClick={() => navigate(crumb.route!)}
className="cursor-pointer"
>
{crumb.label}
</BreadcrumbLink>
) : (
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
)}
</BreadcrumbItem>
</React.Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
</div>
)}
{/* Page Content */}
<main className="flex-1 overflow-y-auto">
{children}
</main>
{/* Footer */}
<footer className="border-t bg-muted/30 px-6 py-3">
<div className="flex justify-center space-x-6 text-sm text-muted-foreground">
<span>© 2024 Kautilya Leadership Centre</span>
<button className="hover:text-foreground transition-colors">Privacy Policy</button>
<button className="hover:text-foreground transition-colors">Terms of Service</button>
</div>
</footer>
</div>
</div>
);
}