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/pages/Dashboard.tsx
priyanshuvish bf42daef0b first commit
2025-09-03 17:02:24 +05:30

534 lines
18 KiB
TypeScript

import React, { use, useState } from 'react';
import { AuthenticatedLayout } from '../layout/AuthenticatedLayout';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Alert, AlertDescription } from '../ui/alert';
import { Progress } from '../ui/progress';
import {
Users,
BookOpen,
DollarSign,
GraduationCap,
TrendingUp,
AlertTriangle,
CheckCircle,
Clock,
Eye,
X,
Plus,
FileText,
Upload,
Globe,
Building,
ExternalLink,
Download,
MoreHorizontal
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
import { useNavigate } from 'react-router-dom';
interface DashboardProps {
// onNavigate: (route: string) => void;
onLogout: () => void;
user: any;
}
interface KPITile {
title: string;
value: string;
change: string;
trend: 'up' | 'down' | 'neutral';
icon: React.ComponentType<{ className?: string }>;
route: string;
}
interface AlertBanner {
id: string;
type: 'info' | 'warning' | 'error';
title: string;
message: string;
dismissible: boolean;
}
interface TaskQueue {
title: string;
count: number;
route: string;
urgent?: boolean;
}
interface ActivityItem {
id: string;
type: string;
user: string;
action: string;
target: string;
timestamp: string;
}
interface QuickAction {
title: string;
description: string;
icon: React.ComponentType<{ className?: string }>;
route: string;
}
const kpiTiles: KPITile[] = [
{
title: 'Total Users',
value: '12,847',
change: '+12.5%',
trend: 'up',
icon: Users,
route: '/users/individual'
},
{
title: 'Active Learners',
value: '8,923',
change: '+8.2%',
trend: 'up',
icon: GraduationCap,
route: '/users/individual'
},
{
title: 'Courses Published',
value: '156',
change: '+3 this week',
trend: 'up',
icon: BookOpen,
route: '/courses'
},
{
title: 'Revenue To-Date',
value: '₹2.4M',
change: '+18.7%',
trend: 'up',
icon: DollarSign,
route: '/admin/analytics'
}
];
const initialAlerts: AlertBanner[] = [
{
id: '1',
type: 'warning',
title: 'System Maintenance',
message: 'Scheduled maintenance window: Tomorrow 2:00 AM - 4:00 AM IST. Some services may be temporarily unavailable.',
dismissible: true
},
{
id: '2',
type: 'info',
title: 'New Feature Available',
message: 'The enhanced analytics dashboard is now live with improved reporting capabilities.',
dismissible: true
}
];
const taskQueues: TaskQueue[] = [
{ title: 'Content Approvals', count: 23, route: '/content', urgent: true },
{ title: 'Role-Grant Requests', count: 7, route: '/admin/roles' },
{ title: 'Testimonials', count: 15, route: '/content' },
{ title: 'Open-Programme Interest', count: 31, route: '/open-programme', urgent: true }
];
const systemHealth = {
uptime: '99.8%',
queueLength: 12,
errorRate: '0.02%'
};
const recentActivities: ActivityItem[] = [
{
id: '1',
type: 'course',
user: 'Dr. Sarah Kumar',
action: 'published',
target: 'Leadership Fundamentals Course',
timestamp: '2 minutes ago'
},
{
id: '2',
type: 'user',
user: 'Admin',
action: 'approved',
target: 'Tech Corp organization registration',
timestamp: '15 minutes ago'
},
{
id: '3',
type: 'content',
user: 'Prof. Rajesh Mehta',
action: 'submitted',
target: 'Strategic Planning article for review',
timestamp: '1 hour ago'
},
{
id: '4',
type: 'programme',
user: 'Admin',
action: 'scheduled',
target: 'Executive Leadership Programme - Batch 2024',
timestamp: '2 hours ago'
},
{
id: '5',
type: 'webinar',
user: 'Dr. Maya Patel',
action: 'registered',
target: '150 participants for Digital Transformation webinar',
timestamp: '3 hours ago'
}
];
const quickActions: QuickAction[] = [
{
title: 'Create Course',
description: 'Build a new course with the course builder',
icon: Plus,
route: '/courses/new'
},
{
title: 'Upload Asset',
description: 'Add new content to the library',
icon: Upload,
route: '/content'
},
{
title: 'Create Landing Page',
description: 'Design a new landing page',
icon: Globe,
route: '/landing-pages'
},
{
title: 'Add HR Org',
description: 'Register a new organization',
icon: Building,
route: '/users/organizations/new'
}
];
const executiveTiles = [
{
title: 'Lead→Enrolment Conversion',
value7d: '23.4%',
value30d: '28.7%',
trend: 'up' as const
},
{
title: 'Programme Fill-Rate',
value7d: '87.2%',
value30d: '84.1%',
trend: 'up' as const
},
{
title: 'Org Activation Rate',
value7d: '76.8%',
value30d: '71.3%',
trend: 'up' as const
},
{
title: '7-Day Revenue Trend',
value7d: '₹180K',
value30d: '₹720K',
trend: 'up' as const
}
];
export function Dashboard({ onLogout, user }: DashboardProps) {
const [alerts, setAlerts] = useState<AlertBanner[]>(initialAlerts);
const [showMoreActivities, setShowMoreActivities] = useState(false);
const dismissAlert = (alertId: string) => {
setAlerts(alerts.filter(alert => alert.id !== alertId));
};
const exportData = (format: 'csv' | 'xlsx' | 'pdf', title: string) => {
// Simulate export functionality
console.log(`Exporting ${title} as ${format.toUpperCase()}`);
};
const breadcrumbs = [{ label: 'Dashboard' }];
const navigate = useNavigate();
return (
<AuthenticatedLayout
currentRoute="/dashboard"
// onNavigate={onNavigate}
onLogout={onLogout}
user={user}
breadcrumbs={breadcrumbs}
>
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
{/* Global KPI Tiles */}
<section aria-labelledby="kpi-section">
<h2 id="kpi-section" className="sr-only">Key Performance Indicators</h2>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{kpiTiles.map((tile, index) => {
const Icon = tile.icon;
return (
<Card key={index} className="cursor-pointer hover:shadow-md transition-shadow">
<CardContent className="p-6" onClick={() => navigate(tile.route)}>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">{tile.title}</p>
<p className="text-2xl font-semibold">{tile.value}</p>
<div className="flex items-center mt-1">
<TrendingUp className={`h-4 w-4 mr-1 ${
tile.trend === 'up' ? 'text-green-500' :
tile.trend === 'down' ? 'text-red-500' :
'text-gray-500'
}`} />
<span className={`text-sm ${
tile.trend === 'up' ? 'text-green-600' :
tile.trend === 'down' ? 'text-red-600' :
'text-gray-600'
}`}>
{tile.change}
</span>
</div>
</div>
<Icon className="h-8 w-8 text-muted-foreground" />
</div>
</CardContent>
</Card>
);
})}
</div>
</section>
{/* Real-Time Alert Banners */}
{alerts.length > 0 && (
<section aria-labelledby="alerts-section">
<h2 id="alerts-section" className="sr-only">System Alerts</h2>
<div className="space-y-3">
{alerts.map((alert) => (
<Alert key={alert.id} variant={alert.type === 'error' ? 'destructive' : 'default'}>
<AlertTriangle className="h-4 w-4" />
<AlertDescription className="flex items-center justify-between">
<div>
<strong>{alert.title}:</strong> {alert.message}
</div>
{alert.dismissible && (
<Button
variant="ghost"
size="sm"
onClick={() => dismissAlert(alert.id)}
className="ml-4 h-6 w-6 p-0"
>
<X className="h-4 w-4" />
</Button>
)}
</AlertDescription>
</Alert>
))}
</div>
</section>
)}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
{/* Task Queues */}
<section aria-labelledby="tasks-section" className="xl:col-span-2">
<Card>
<CardHeader>
<CardTitle id="tasks-section">Task Queues</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{taskQueues.map((queue, index) => (
<div
key={index}
className="flex items-center justify-between p-4 border rounded-lg cursor-pointer hover:bg-accent transition-colors"
onClick={() => navigate(queue.route)}
>
<div>
<h4 className="font-medium">{queue.title}</h4>
<div className="flex items-center mt-1">
<span className="text-2xl font-semibold mr-2">{queue.count}</span>
{queue.urgent && (
<Badge variant="destructive" className="text-xs">Urgent</Badge>
)}
</div>
</div>
<Eye className="h-5 w-5 text-muted-foreground" />
</div>
))}
</div>
</CardContent>
</Card>
</section>
{/* System Health */}
<section aria-labelledby="health-section">
<Card>
<CardHeader>
<CardTitle id="health-section">System Health</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<div className="flex justify-between text-sm mb-1">
<span>Uptime</span>
<span className="text-green-600 font-medium">{systemHealth.uptime}</span>
</div>
<Progress value={99.8} className="h-2" />
</div>
<div>
<div className="flex justify-between text-sm">
<span>Queue Length</span>
<span className="font-medium">{systemHealth.queueLength}</span>
</div>
</div>
<div>
<div className="flex justify-between text-sm">
<span>Error Rate</span>
<span className="text-green-600 font-medium">{systemHealth.errorRate}</span>
</div>
</div>
<div className="pt-2 border-t">
<div className="flex items-center text-sm text-green-600">
<CheckCircle className="h-4 w-4 mr-2" />
All systems operational
</div>
</div>
</CardContent>
</Card>
</section>
</div>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
{/* Recent Activity Feed */}
<section aria-labelledby="activity-section">
<Card>
<CardHeader>
<CardTitle id="activity-section">Recent Activity</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{recentActivities.slice(0, showMoreActivities ? recentActivities.length : 5).map((activity) => (
<div key={activity.id} className="flex items-start space-x-3 pb-3 last:pb-0 border-b last:border-0">
<div className="flex-shrink-0 mt-0.5">
<div className="h-2 w-2 bg-blue-500 rounded-full"></div>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm">
<span className="font-medium">{activity.user}</span>
{' '}{activity.action}{' '}
<span className="font-medium">{activity.target}</span>
</p>
<div className="flex items-center mt-1">
<Clock className="h-3 w-3 text-muted-foreground mr-1" />
<span className="text-xs text-muted-foreground">{activity.timestamp}</span>
</div>
</div>
</div>
))}
</div>
{recentActivities.length > 5 && (
<Button
variant="ghost"
onClick={() => setShowMoreActivities(!showMoreActivities)}
className="w-full mt-4"
>
{showMoreActivities ? 'Show Less' : `Show ${recentActivities.length - 5} More`}
</Button>
)}
</CardContent>
</Card>
</section>
{/* Quick Actions */}
<section aria-labelledby="actions-section">
<Card>
<CardHeader>
<CardTitle id="actions-section">Quick Actions</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{quickActions.map((action, index) => {
const Icon = action.icon;
return (
<Button
key={index}
variant="outline"
className="h-auto p-4 flex flex-col items-start text-left hover:bg-accent transition-colors"
onClick={() => navigate(action.route)}
>
<Icon className="h-5 w-5 mb-2" style={{ color: 'var(--color-brand-primary)' }} />
<div>
<div className="font-medium">{action.title}</div>
<div className="text-xs text-muted-foreground mt-1">{action.description}</div>
</div>
</Button>
);
})}
</div>
</CardContent>
</Card>
</section>
</div>
{/* Executive Insight Tiles */}
<section aria-labelledby="insights-section">
<Card>
<CardHeader>
<CardTitle id="insights-section">Executive Insights</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{executiveTiles.map((tile, index) => (
<div key={index} className="space-y-2">
<h4 className="font-medium text-sm">{tile.title}</h4>
<div className="space-y-1">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">7-Day</span>
<span className="font-semibold">{tile.value7d}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">30-Day</span>
<span className="font-semibold">{tile.value30d}</span>
</div>
</div>
<div className="flex items-center justify-between pt-2 border-t">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<Download className="h-3 w-3 mr-1" />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => exportData('csv', tile.title)}>
Export CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => exportData('xlsx', tile.title)}>
Export XLSX
</DropdownMenuItem>
<DropdownMenuItem onClick={() => exportData('pdf', tile.title)}>
Export PDF
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant="ghost"
size="sm"
onClick={() => navigate('/admin/analytics')}
>
<ExternalLink className="h-3 w-3 mr-1" />
Open in Analytics
</Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</section>
</div>
</AuthenticatedLayout>
);
}