Files
KLC-Hr-Dashboard-Frontend/src/components/LearningAnalyticsTable.tsx
2025-09-26 16:37:17 +05:30

502 lines
18 KiB
TypeScript

import React, { useState } from 'react';
import { Button } from './ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
import { Progress } from './ui/progress';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './ui/sheet';
import {
Eye,
Send,
BarChart3,
User,
BookOpen,
Clock,
TrendingUp
} from 'lucide-react';
interface CurrentItem {
type: 'course' | 'content';
id: string;
title: string;
status: 'Not Started' | 'In-Progress' | 'Completed';
}
interface NextItem {
type: 'course' | 'content';
id: string;
title: string;
}
interface LearnerAnalytics {
learnerId: string;
learnerName: string;
learnerEmail: string;
currentItem?: CurrentItem;
progressPct: number;
nextItem?: NextItem;
lastActivity: Date;
}
interface LearningAnalyticsData {
programmeId: string;
rows: LearnerAnalytics[];
}
interface LearningAnalyticsTableProps {
analyticsData?: LearningAnalyticsData[];
onViewLearner?: (learnerId: string) => void;
onNudgeLearner?: (learnerId: string) => void;
onViewAllAnalytics?: (programmeId: string) => void;
}
const mockAnalyticsData: LearningAnalyticsData[] = [
{
programmeId: 'prog-001',
rows: [
{
learnerId: 'learner-001',
learnerName: 'Sarah Chen',
learnerEmail: 'sarah.chen@company.com',
currentItem: {
type: 'course',
id: 'course-001',
title: 'Strategic Thinking',
status: 'In-Progress'
},
progressPct: 85,
nextItem: {
type: 'course',
id: 'course-002',
title: 'Decision Making'
},
lastActivity: new Date('2024-12-27T14:30:00')
},
{
learnerId: 'learner-002',
learnerName: 'Michael Rodriguez',
learnerEmail: 'michael.r@company.com',
currentItem: {
type: 'content',
id: 'content-003',
title: 'Leadership Styles Assessment',
status: 'Not Started'
},
progressPct: 62,
nextItem: {
type: 'course',
id: 'course-003',
title: 'Team Management'
},
lastActivity: new Date('2024-12-26T09:15:00')
},
{
learnerId: 'learner-003',
learnerName: 'Emma Thompson',
learnerEmail: 'emma.thompson@company.com',
currentItem: {
type: 'course',
id: 'course-002',
title: 'Decision Making',
status: 'Completed'
},
progressPct: 94,
nextItem: {
type: 'content',
id: 'content-005',
title: 'Leadership Reflection Journal'
},
lastActivity: new Date('2024-12-27T16:45:00')
},
{
learnerId: 'learner-004',
learnerName: 'David Kim',
learnerEmail: 'david.kim@company.com',
currentItem: {
type: 'course',
id: 'course-001',
title: 'Strategic Thinking',
status: 'In-Progress'
},
progressPct: 78,
nextItem: {
type: 'course',
id: 'course-002',
title: 'Decision Making'
},
lastActivity: new Date('2024-12-27T11:20:00')
}
]
},
{
programmeId: 'prog-002',
rows: [
{
learnerId: 'learner-005',
learnerName: 'Lisa Wang',
learnerEmail: 'lisa.wang@company.com',
currentItem: {
type: 'course',
id: 'course-101',
title: 'JavaScript Fundamentals',
status: 'In-Progress'
},
progressPct: 56,
nextItem: {
type: 'course',
id: 'course-102',
title: 'React Basics'
},
lastActivity: new Date('2024-12-27T13:10:00')
},
{
learnerId: 'learner-006',
learnerName: 'James Wilson',
learnerEmail: 'james.wilson@company.com',
currentItem: {
type: 'content',
id: 'content-201',
title: 'API Design Best Practices',
status: 'Not Started'
},
progressPct: 34,
nextItem: {
type: 'course',
id: 'course-103',
title: 'Database Design'
},
lastActivity: new Date('2024-12-25T15:30:00')
}
]
}
];
const programmeNames = {
'prog-001': 'Leadership Development',
'prog-002': 'Technical Skills Bootcamp',
'prog-003': 'Communication Excellence',
'prog-004': 'Project Management Certification'
};
export const LearningAnalyticsTable: React.FC<LearningAnalyticsTableProps> = ({
analyticsData = mockAnalyticsData,
onViewLearner,
onNudgeLearner,
onViewAllAnalytics
}) => {
const [selectedProgramme, setSelectedProgramme] = useState(analyticsData[0]?.programmeId || '');
const [selectedLearner, setSelectedLearner] = useState<LearnerAnalytics | null>(null);
const [isLearnerDrawerOpen, setIsLearnerDrawerOpen] = useState(false);
const currentProgrammeData = analyticsData.find(data => data.programmeId === selectedProgramme);
const currentProgrammeName = programmeNames[selectedProgramme as keyof typeof programmeNames] || 'Unknown Programme';
const formatLastActivity = (date: Date) => {
const now = new Date();
const diffHours = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60));
if (diffHours < 1) {
return 'Just now';
} else if (diffHours < 24) {
return `${diffHours} hours ago`;
} else {
const diffDays = Math.floor(diffHours / 24);
return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
}
};
const getStatusBadgeProps = (status: CurrentItem['status']) => {
switch (status) {
case 'Completed':
return { variant: 'default' as const, className: 'bg-status-success text-status-success-foreground' };
case 'In-Progress':
return { variant: 'secondary' as const, className: 'bg-status-warn text-status-warn-foreground' };
case 'Not Started':
return { variant: 'outline' as const, className: 'border-status-error text-status-error' };
default:
return { variant: 'secondary' as const };
}
};
const handleViewLearner = (learner: LearnerAnalytics) => {
setSelectedLearner(learner);
setIsLearnerDrawerOpen(true);
onViewLearner?.(learner.learnerId);
};
const handleNudgeLearner = (learnerId: string) => {
onNudgeLearner?.(learnerId);
console.log(`Sent nudge to learner: ${learnerId}`);
};
const handleViewAllAnalytics = () => {
onViewAllAnalytics?.(selectedProgramme);
console.log(`Viewing all analytics for programme: ${selectedProgramme}`);
};
return (
<Card>
<CardHeader>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<CardTitle>Learning Analytics</CardTitle>
<CardDescription>Per-programme learner progress and current activities</CardDescription>
</div>
<div className="flex items-center gap-2">
<Select value={selectedProgramme} onValueChange={setSelectedProgramme}>
<SelectTrigger className="w-[250px]">
<SelectValue placeholder="Select programme" />
</SelectTrigger>
<SelectContent>
{analyticsData.map((data) => (
<SelectItem key={data.programmeId} value={data.programmeId}>
{programmeNames[data.programmeId as keyof typeof programmeNames] || data.programmeId}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="outline"
onClick={handleViewAllAnalytics}
className="min-tap-44"
>
<BarChart3 className="h-4 w-4 mr-2" />
View All in Analytics
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Programme Summary */}
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
<div>
<h4 className="font-medium">{currentProgrammeName}</h4>
<p className="text-sm text-muted-foreground">
{currentProgrammeData?.rows.length || 0} learners enrolled
</p>
</div>
<div className="text-right">
<p className="text-2xl font-bold">
{currentProgrammeData ? Math.round(
currentProgrammeData.rows.reduce((sum, learner) => sum + learner.progressPct, 0) /
currentProgrammeData.rows.length
) : 0}%
</p>
<p className="text-sm text-muted-foreground">Average Progress</p>
</div>
</div>
{/* Analytics Table */}
<div className="rounded-md border">
<Table>
<TableHeader className="sticky-header">
<TableRow>
<TableHead className="w-[200px]">Learner</TableHead>
<TableHead className="w-[250px]">Current Item</TableHead>
<TableHead className="w-[120px]">Progress</TableHead>
<TableHead className="w-[200px]">Next Item</TableHead>
<TableHead className="w-[120px]">Last Activity</TableHead>
<TableHead className="w-[120px]">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{currentProgrammeData?.rows.map((learner) => (
<TableRow key={learner.learnerId} className="min-h-[44px]">
<TableCell>
<div>
<p className="font-medium">{learner.learnerName}</p>
<p className="text-sm text-muted-foreground">{learner.learnerEmail}</p>
</div>
</TableCell>
<TableCell>
{learner.currentItem ? (
<div className="space-y-1">
<div className="flex items-center gap-2">
{learner.currentItem.type === 'course' ? (
<BookOpen className="h-4 w-4 text-muted-foreground" />
) : (
<div className="h-4 w-4 bg-muted-foreground rounded-sm" />
)}
<span className="text-sm font-medium">{learner.currentItem.title}</span>
</div>
<Badge
{...getStatusBadgeProps(learner.currentItem.status)}
className="text-xs"
>
{learner.currentItem.status}
</Badge>
</div>
) : (
<span className="text-muted-foreground text-sm">No current item</span>
)}
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Progress
value={learner.progressPct}
className="w-16"
aria-describedby={`progress-${learner.learnerId}`}
/>
<span className="text-sm font-medium">{learner.progressPct}%</span>
</div>
<span id={`progress-${learner.learnerId}`} className="sr-only">
Progress: {learner.progressPct} percent complete
</span>
</div>
</TableCell>
<TableCell>
{learner.nextItem ? (
<div className="flex items-center gap-2">
{learner.nextItem.type === 'course' ? (
<BookOpen className="h-4 w-4 text-muted-foreground" />
) : (
<div className="h-4 w-4 bg-muted-foreground rounded-sm" />
)}
<span className="text-sm">{learner.nextItem.title}</span>
</div>
) : (
<span className="text-muted-foreground text-sm">Programme complete</span>
)}
</TableCell>
<TableCell>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Clock className="h-4 w-4" />
<span>{formatLastActivity(learner.lastActivity)}</span>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleViewLearner(learner)}
className="min-tap-44"
aria-label={`View details for ${learner.learnerName}`}
>
<Eye className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleNudgeLearner(learner.learnerId)}
className="min-tap-44"
aria-label={`Send reminder to ${learner.learnerName}`}
>
<Send className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
)) || (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground">
No learner data available for this programme.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
{/* Learner Detail Drawer */}
<Sheet open={isLearnerDrawerOpen} onOpenChange={setIsLearnerDrawerOpen}>
<SheetContent
className="w-[500px] sm:w-[600px]"
role="dialog"
aria-modal="true"
aria-labelledby="learner-detail-title"
>
<SheetHeader>
<SheetTitle id="learner-detail-title">
{selectedLearner?.learnerName}
</SheetTitle>
<SheetDescription>
Detailed progress and activity in {currentProgrammeName}
</SheetDescription>
</SheetHeader>
{selectedLearner && (
<div className="mt-6 space-y-6">
{/* Contact Info */}
<div className="space-y-2">
<h4 className="font-medium">Contact Information</h4>
<div className="space-y-1">
<p className="text-sm text-muted-foreground">{selectedLearner.learnerEmail}</p>
<p className="text-sm text-muted-foreground">
Last active: {formatLastActivity(selectedLearner.lastActivity)}
</p>
</div>
</div>
{/* Progress Summary */}
<div className="space-y-2">
<h4 className="font-medium">Progress Summary</h4>
<div className="p-4 bg-muted/30 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-sm">Overall Progress</span>
<span className="font-medium">{selectedLearner.progressPct}%</span>
</div>
<Progress value={selectedLearner.progressPct} className="h-2" />
</div>
</div>
{/* Current Activity */}
{selectedLearner.currentItem && (
<div className="space-y-2">
<h4 className="font-medium">Current Activity</h4>
<div className="p-4 border rounded-lg">
<div className="flex items-center gap-2 mb-2">
{selectedLearner.currentItem.type === 'course' ? (
<BookOpen className="h-4 w-4 text-muted-foreground" />
) : (
<div className="h-4 w-4 bg-muted-foreground rounded-sm" />
)}
<span className="font-medium">{selectedLearner.currentItem.title}</span>
</div>
<Badge {...getStatusBadgeProps(selectedLearner.currentItem.status)}>
{selectedLearner.currentItem.status}
</Badge>
</div>
</div>
)}
{/* Next Up */}
{selectedLearner.nextItem && (
<div className="space-y-2">
<h4 className="font-medium">Next Up</h4>
<div className="p-4 border border-dashed rounded-lg">
<div className="flex items-center gap-2">
{selectedLearner.nextItem.type === 'course' ? (
<BookOpen className="h-4 w-4 text-muted-foreground" />
) : (
<div className="h-4 w-4 bg-muted-foreground rounded-sm" />
)}
<span>{selectedLearner.nextItem.title}</span>
</div>
</div>
</div>
)}
{/* Actions */}
<div className="flex gap-2 pt-4">
<Button className="flex-1">
<Send className="h-4 w-4 mr-2" />
Send Reminder
</Button>
<Button variant="outline" className="flex-1">
<TrendingUp className="h-4 w-4 mr-2" />
View Full Analytics
</Button>
</div>
</div>
)}
</SheetContent>
</Sheet>
</CardContent>
</Card>
);
};