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
2025-09-26 19:45:02 +05:30

577 lines
18 KiB
TypeScript

import React, { 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 { Input } from '../ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
import { Progress } from '../ui/progress';
import { toast } from "sonner@2.0.3";
import {
Plus,
Upload,
Target,
Folder,
Building,
Users,
Globe,
BarChart3,
ExternalLink,
MoreHorizontal,
Eye,
ChevronLeft,
ChevronRight,
Calendar,
Filter,
TrendingUp,
TrendingDown,
Minus,
GraduationCap,
BookOpen,
Award
} from 'lucide-react';
import {
dashboardKPIs,
mockUsers,
mockCourses,
mockProgrammes,
mockContent,
mockLeads
} from '../../data/mockData';
interface DashboardProps {
onNavigate: (route: string) => void;
onLogout: () => void;
user: any;
}
// Quick Actions - exactly as specified
const quickActions = [
{
title: "Create Course",
route: "/courses/new",
icon: Plus,
isPrimary: true
},
{
title: "Upload Content",
route: "/content",
icon: Upload,
isPrimary: false
},
{
title: "New Profiler",
route: "/profilers/new",
icon: Target,
isPrimary: false
},
{
title: "Profiler Preview",
route: "/profilers/preview",
icon: Eye,
isPrimary: false
},
{
title: "Create Programme",
route: "/programmes/new",
icon: Folder,
isPrimary: false
},
{
title: "Add Organization",
route: "/users/organizations/new",
icon: Building,
isPrimary: false
},
{
title: "Invite Learners",
route: "/users/individual",
icon: Users,
isPrimary: false
},
{
title: "Create 360 Tour",
route: "/facilities-360/new",
icon: Building,
isPrimary: false
},
{
title: "Open Analytics",
route: "/admin/analytics",
icon: BarChart3,
isPrimary: false
}
];
// Enhanced KPI Cards with real data
const getKPICards = () => [
{
title: "Live Organizations",
route: "/users/organizations",
value: mockUsers.filter(u => u.organization).length.toString(),
trend: 12.5,
icon: Building,
description: "Active organizational clients"
},
{
title: "Live Learners",
route: "/users/individual",
value: mockUsers.length.toString(),
trend: 8.2,
icon: Users,
description: "Total enrolled learners"
},
{
title: "Live Programmes",
route: "/programmes",
value: mockProgrammes.filter(p => p.status === 'active').length.toString(),
trend: 15.3,
icon: Award,
description: "Currently running programmes"
}
];
// Mock programme details data
const mockProgrammeDetails = [
{
id: 1,
programme: "Executive Leadership Program",
organization: "Tech Corp India",
users: 45,
hrAdmin: "Priya Patel",
klcAdmin: "Dr. Rajesh Mehta",
status: "Active",
startDate: "2024-02-01",
endDate: "2024-07-31",
progress: 65
},
{
id: 2,
programme: "Digital Innovation Diploma",
organization: "Innovation Hub",
users: 32,
hrAdmin: "Rohit Gupta",
klcAdmin: "Prof. Sunita Agarwal",
status: "Upcoming",
startDate: "2024-08-01",
endDate: "2025-07-31",
progress: 0
},
{
id: 3,
programme: "Strategic Management Course",
organization: "Delhi University",
users: 78,
hrAdmin: "Ananya Singh",
klcAdmin: "Dr. Amit Sharma",
status: "Active",
startDate: "2024-01-15",
endDate: "2024-06-15",
progress: 85
}
];
export function Dashboard({ onNavigate, onLogout, user }: DashboardProps) {
const [programmeFilter, setProgrammeFilter] = useState("all");
const [statusFilter, setStatusFilter] = useState("all");
const [dateRangeFrom, setDateRangeFrom] = useState("");
const [dateRangeTo, setDateRangeTo] = useState("");
const breadcrumbs = [
{ label: "Dashboard" }
];
const kpiCards = getKPICards();
const filteredProgrammeDetails = mockProgrammeDetails.filter(programme => {
const matchesStatus = statusFilter === "all" || programme.status.toLowerCase() === statusFilter;
const matchesProgramme = programmeFilter === "all" ||
programme.programme.toLowerCase().includes(programmeFilter.toLowerCase());
return matchesStatus && matchesProgramme;
});
const handleApplyFilters = () => {
toast.success("Filters applied successfully", {
duration: 2000
});
};
const handleResetFilters = () => {
setProgrammeFilter("all");
setStatusFilter("all");
setDateRangeFrom("");
setDateRangeTo("");
toast.success("Filters reset to defaults", {
duration: 2000
});
};
const handleViewDetails = (route: string, title: string) => {
onNavigate(route);
};
const getStatusBadgeVariant = (status: string) => {
switch (status.toLowerCase()) {
case "active": return "default";
case "upcoming": return "secondary";
case "completed": return "outline";
default: return "secondary";
}
};
const getTrendIcon = (trend: number) => {
if (trend > 0) return <TrendingUp className="h-4 w-4 text-green-500" />;
if (trend < 0) return <TrendingDown className="h-4 w-4 text-red-500" />;
return <Minus className="h-4 w-4 text-muted-foreground" />;
};
const renderQuickActions = () => (
<section aria-labelledby="quick-actions-section" className="space-y-4">
<h2 id="quick-actions-section" className="sr-only">Quick Actions</h2>
<div className="flex flex-wrap gap-3">
{quickActions.map((action, index) => {
const Icon = action.icon;
const buttonLabels = [
"Create Course",
"Upload Content",
"Create Profiler",
"Preview Profiler",
"Create Programme",
"Add Organization",
"Add Individual",
"Create 360 Tour",
"View Analytics"
];
return (
<Button
key={index}
onClick={() => onNavigate(action.route)}
className={`min-h-[44px] focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50 ${
action.isPrimary
? ''
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'
}`}
style={action.isPrimary ? { backgroundColor: "var(--color-brand-primary)" } : {}}
variant={action.isPrimary ? "default" : "secondary"}
>
<Icon className="h-4 w-4 mr-2" />
{buttonLabels[index] || action.label}
</Button>
);
})}
</div>
</section>
);
const renderKPICards = () => (
<section aria-labelledby="kpi-section" className="space-y-4">
<h2 id="kpi-section" className="sr-only">Key Performance Indicators</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{kpiCards.map((kpi, index) => {
const Icon = kpi.icon;
return (
<Card key={index} className="w-full">
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="space-y-2 flex-1">
<div className="flex items-center gap-2">
<Icon className="h-5 w-5 text-muted-foreground" />
<h3 className="text-sm font-medium text-foreground">{kpi.title}</h3>
</div>
<div className="text-3xl font-bold text-foreground">{kpi.value}</div>
<div className="flex items-center gap-2">
{getTrendIcon(kpi.trend)}
<span className={`text-sm ${kpi.trend > 0 ? 'text-green-600' : kpi.trend < 0 ? 'text-red-600' : 'text-muted-foreground'}`}>
{kpi.trend > 0 ? '+' : ''}{kpi.trend}% vs last month
</span>
</div>
<p className="text-xs text-muted-foreground">{kpi.description}</p>
</div>
<div
className="w-1 h-16 rounded"
style={{ backgroundColor: "var(--color-brand-primary)" }}
/>
</div>
<div className="mt-4">
<Button
variant="ghost"
className="h-auto p-0 text-sm hover:underline focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50"
style={{ color: "var(--color-brand-primary)" }}
onClick={() => handleViewDetails(kpi.route, kpi.title)}
>
View details
<ExternalLink className="h-3 w-3 ml-1" />
</Button>
</div>
</CardContent>
</Card>
);
})}
</div>
</section>
);
const renderActivitySummary = () => (
<section aria-labelledby="activity-summary-section" className="space-y-4">
<h2 id="activity-summary-section" className="sr-only">Activity Summary</h2>
</section>
);
const renderProgrammeDetails = () => (
<section aria-labelledby="programme-details-section" className="space-y-4">
<Card>
<CardHeader>
<CardTitle id="programme-details-section">Programme-wise details</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Filters Row */}
<div className="flex flex-wrap items-center gap-4 justify-end">
<div className="flex items-center gap-2">
<Select value={programmeFilter} onValueChange={setProgrammeFilter}>
<SelectTrigger className="w-[140px] min-h-[44px]">
<SelectValue placeholder="Programme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Programmes</SelectItem>
<SelectItem value="leadership">Leadership</SelectItem>
<SelectItem value="management">Management</SelectItem>
<SelectItem value="digital">Digital</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[120px] min-h-[44px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="completed">Completed</SelectItem>
<SelectItem value="upcoming">Upcoming</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center gap-2">
<Input
type="date"
value={dateRangeFrom}
onChange={(e) => setDateRangeFrom(e.target.value)}
className="w-[140px] min-h-[44px]"
placeholder="From"
/>
<span className="text-muted-foreground">to</span>
<Input
type="date"
value={dateRangeTo}
onChange={(e) => setDateRangeTo(e.target.value)}
className="w-[140px] min-h-[44px]"
placeholder="To"
/>
</div>
<Button
onClick={handleApplyFilters}
className="min-h-[44px]"
style={{ backgroundColor: "var(--color-brand-primary)" }}
>
Apply
</Button>
<Button
variant="outline"
onClick={handleResetFilters}
className="min-h-[44px]"
>
Reset
</Button>
</div>
</div>
{/* Table */}
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader className="bg-muted/50 sticky top-0">
<TableRow>
<TableHead className="min-w-[200px]">Programme</TableHead>
<TableHead>Organization</TableHead>
<TableHead>No. of Users</TableHead>
<TableHead>HR Admin</TableHead>
<TableHead>KLC Admin</TableHead>
<TableHead>Programme Status</TableHead>
<TableHead>Start Date</TableHead>
<TableHead>End Date</TableHead>
<TableHead>Progress</TableHead>
<TableHead className="w-12">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredProgrammeDetails.length === 0 ? (
<TableRow>
<TableCell colSpan={10} className="text-center py-8 text-muted-foreground">
No programme data for the selected filters.
</TableCell>
</TableRow>
) : (
filteredProgrammeDetails.map((programme) => (
<TableRow key={programme.id}>
<TableCell>
<div>
<p className="font-medium">{programme.programme}</p>
</div>
</TableCell>
<TableCell>{programme.organization}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-muted-foreground" />
{programme.users}
</div>
</TableCell>
<TableCell>{programme.hrAdmin}</TableCell>
<TableCell>{programme.klcAdmin}</TableCell>
<TableCell>
<Badge variant={getStatusBadgeVariant(programme.status)}>
{programme.status}
</Badge>
</TableCell>
<TableCell className="text-sm">{programme.startDate}</TableCell>
<TableCell className="text-sm">{programme.endDate}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Progress value={programme.progress} className="w-16" />
<span className="text-xs text-muted-foreground">{programme.progress}%</span>
</div>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onNavigate('/programmes')}>
<Eye className="h-4 w-4 mr-2" />
View Details
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onNavigate('/admin/analytics')}>
<BarChart3 className="h-4 w-4 mr-2" />
Analytics
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Footer with pagination */}
<div className="flex items-center justify-between">
<div className="text-sm text-muted-foreground">
Showing {filteredProgrammeDetails.length} of {mockProgrammeDetails.length} results
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
disabled
className="min-h-[44px] w-[44px] p-0"
>
<ChevronLeft className="h-4 w-4" />
<span className="sr-only">Previous page</span>
</Button>
<Button
variant="outline"
size="sm"
disabled
className="min-h-[44px] w-[44px] p-0"
>
<ChevronRight className="h-4 w-4" />
<span className="sr-only">Next page</span>
</Button>
</div>
</div>
</CardContent>
</Card>
</section>
);
return (
<AuthenticatedLayout
currentRoute="/dashboard"
onNavigate={onNavigate}
onLogout={onLogout}
user={user}
breadcrumbs={breadcrumbs}
>
<div className="p-6 space-y-8 max-w-[1440px] mx-auto">
{/* Header Row */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h1>Dashboard</h1>
{user.role === "Super Admin" && (
<Badge
variant="outline"
className="text-xs"
style={{
borderColor: "var(--color-brand-primary)",
color: "var(--color-brand-primary)"
}}
>
Super Admin
</Badge>
)}
</div>
<div className="text-sm text-muted-foregrounde">
Last updated: {new Date().toLocaleString()}
</div>
</div>
{/* Quick Actions */}
{renderQuickActions()}
{/* KPI Strip */}
{renderKPICards()}
{/* Activity Summary */}
{renderActivitySummary()}
{/* Programme-wise Details */}
{renderProgrammeDetails()}
{/* Toast area for system messages */}
<div
role="status"
aria-live="polite"
aria-label="System status messages"
className="sr-only"
>
{/* Toast messages will appear here via the toast system */}
</div>
</div>
</AuthenticatedLayout>
);
}