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

902 lines
37 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Button } from './ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from './ui/dialog';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './ui/sheet';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
import { Progress } from './ui/progress';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { Alert, AlertDescription } from './ui/alert';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion';
import {
ArrowLeft,
Download,
BarChart3,
MoreHorizontal,
Users,
Calendar,
Clock,
BookOpen,
Award,
Bell,
Search,
Filter,
Eye,
Mail,
FileText,
Play,
MapPin,
Video,
FileQuestion,
Activity,
Building2,
RefreshCw,
ExternalLink,
Plus,
CheckCircle,
AlertCircle,
XCircle
} from 'lucide-react';
// Types
interface Programme {
id: string;
title: string;
status: 'Active' | 'Upcoming' | 'Completed';
code: string;
owner: string;
version: number;
description: string;
goals: string[];
tags: string[];
structure: {
preAssessment: ProgrammeItem[];
preLearning: ProgrammeItem[];
classroom: ProgrammeItem[];
postLearning: ProgrammeItem[];
};
}
interface ProgrammeItem {
id: string;
type: 'Profiler' | 'Course' | 'Content' | 'Webinar' | 'OfflineSession';
title: string;
duration?: string;
dueDate?: string;
venue?: string;
room?: string;
date?: string;
capacity?: number;
status?: 'Not Started' | 'In Progress' | 'Completed';
}
interface Assignment {
startDate: string;
endDate: string;
orgId: string;
orgName: string;
}
interface ProgrammeCounts {
learners: number;
completionPct: number;
courses: number;
content: number;
webinars: number;
classes: number;
}
interface ProgrammeLearner {
id: string;
name: string;
email: string;
currentItem: {
type: string;
id: string;
title: string;
status: 'Not Started' | 'In-Progress' | 'Completed';
};
progressPct: number;
nextItem?: {
type: string;
id: string;
title: string;
};
lastActivity: string;
stage: 'Pre-assessment' | 'Pre-learning' | 'Classroom' | 'Post-learning';
cohort?: string;
}
interface ProgrammeEvent {
id: string;
programmeId: string;
type: 'webinar' | 'class' | 'course_end' | 'content_end' | 'programme_end';
title: string;
start: string;
end: string;
venue?: string;
room?: string;
}
interface ProgrammeHRViewProps {
programmeId: string;
onBack: () => void;
onAssignLearners: (programmeId: string) => void;
onDownloadTracker: (programmeId: string) => void;
onOpenAnalytics: (programmeId: string) => void;
}
// Mock data
const mockProgramme: Programme = {
id: 'prg_123',
title: 'Executive Leadership Development Programme',
status: 'Active',
code: 'ELDP-2024',
owner: 'Dr. Sarah Johnson',
version: 2,
description: 'A comprehensive leadership development programme designed to build strategic thinking, emotional intelligence, and decision-making capabilities for senior executives.',
goals: [
'Develop strategic thinking and planning capabilities',
'Enhance emotional intelligence and self-awareness',
'Build effective communication and influence skills',
'Master change management and innovation leadership'
],
tags: ['Leadership', 'Executive', 'Strategic Thinking', 'Management'],
structure: {
preAssessment: [
{ id: 'pa1', type: 'Profiler', title: 'Leadership Style Assessment', status: 'Completed' },
{ id: 'pa2', type: 'Profiler', title: '360-Degree Feedback', status: 'In Progress' }
],
preLearning: [
{ id: 'pl1', type: 'Course', title: 'Strategic Thinking Fundamentals', duration: '4 hours', dueDate: '2024-01-15', status: 'Completed' },
{ id: 'pl2', type: 'Content', title: 'Leadership in Crisis Webcast', duration: '45 mins', status: 'In Progress' },
{ id: 'pl3', type: 'Webinar', title: 'Future of Leadership', date: '2024-01-20 10:00 AM AEDT', status: 'Not Started' }
],
classroom: [
{ id: 'c1', type: 'OfflineSession', title: 'Strategic Leadership Workshop', venue: 'Sydney Campus', room: 'Executive Suite A', date: '2024-02-05', capacity: 20 },
{ id: 'c2', type: 'OfflineSession', title: 'Case Study Analysis', venue: 'Sydney Campus', room: 'Conference Room B', date: '2024-02-06', capacity: 20 }
],
postLearning: [
{ id: 'po1', type: 'Course', title: 'Advanced Decision Making', duration: '6 hours', dueDate: '2024-02-20', status: 'Not Started' },
{ id: 'po2', type: 'Content', title: 'Leadership Reflection Journal', duration: '2 weeks', status: 'Not Started' }
]
}
};
const mockAssignment: Assignment = {
startDate: '2024-01-01',
endDate: '2024-03-31',
orgId: 'org_123',
orgName: 'Tech Solutions Pvt Ltd'
};
const mockCounts: ProgrammeCounts = {
learners: 28,
completionPct: 64,
courses: 2,
content: 2,
webinars: 1,
classes: 2
};
const mockLearners: ProgrammeLearner[] = [
{
id: 'l1',
name: 'Sarah Chen',
email: 'sarah.chen@company.com',
currentItem: { type: 'Course', id: 'pl1', title: 'Strategic Thinking Fundamentals', status: 'In-Progress' },
progressPct: 75,
nextItem: { type: 'Content', id: 'pl2', title: 'Leadership in Crisis Webcast' },
lastActivity: '2 hours ago',
stage: 'Pre-learning',
cohort: 'Cohort A'
},
{
id: 'l2',
name: 'Michael Rodriguez',
email: 'michael.r@company.com',
currentItem: { type: 'Profiler', id: 'pa2', title: '360-Degree Feedback', status: 'In-Progress' },
progressPct: 45,
nextItem: { type: 'Course', id: 'pl1', title: 'Strategic Thinking Fundamentals' },
lastActivity: '1 day ago',
stage: 'Pre-assessment',
cohort: 'Cohort A'
},
{
id: 'l3',
name: 'Emma Thompson',
email: 'emma.thompson@company.com',
currentItem: { type: 'OfflineSession', id: 'c1', title: 'Strategic Leadership Workshop', status: 'Completed' },
progressPct: 89,
nextItem: { type: 'Course', id: 'po1', title: 'Advanced Decision Making' },
lastActivity: '3 hours ago',
stage: 'Classroom',
cohort: 'Cohort B'
}
];
const mockEvents: ProgrammeEvent[] = [
{
id: 'e1',
programmeId: 'prg_123',
type: 'webinar',
title: 'Future of Leadership',
start: '2024-01-20T10:00:00',
end: '2024-01-20T11:30:00'
},
{
id: 'e2',
programmeId: 'prg_123',
type: 'class',
title: 'Strategic Leadership Workshop',
start: '2024-02-05T09:00:00',
end: '2024-02-05T17:00:00',
venue: 'Sydney Campus',
room: 'Executive Suite A'
}
];
export const ProgrammeHRView: React.FC<ProgrammeHRViewProps> = ({
programmeId,
onBack,
onAssignLearners,
onDownloadTracker,
onOpenAnalytics
}) => {
const [activeTab, setActiveTab] = useState('overview');
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [stageFilter, setStageFilter] = useState('all');
const [cohortFilter, setCohortFilter] = useState('all');
const [selectedLearner, setSelectedLearner] = useState<ProgrammeLearner | null>(null);
const [showLearnerDrawer, setShowLearnerDrawer] = useState(false);
const [exporting, setExporting] = useState(false);
// Simulate loading
useEffect(() => {
const timer = setTimeout(() => setLoading(false), 800);
return () => clearTimeout(timer);
}, []);
const getTypeIcon = (type: string) => {
switch (type) {
case 'Course': return <BookOpen className="h-4 w-4" />;
case 'Content': return <FileText className="h-4 w-4" />;
case 'Webinar': return <Video className="h-4 w-4" />;
case 'Profiler': return <FileQuestion className="h-4 w-4" />;
case 'OfflineSession': return <Building2 className="h-4 w-4" />;
default: return <BookOpen className="h-4 w-4" />;
}
};
const getStatusBadge = (status?: string) => {
switch (status) {
case 'Completed':
return <Badge variant="default" className="bg-status-success text-white"><CheckCircle className="h-3 w-3 mr-1" />Completed</Badge>;
case 'In Progress':
return <Badge variant="secondary" className="bg-status-warn text-black"><AlertCircle className="h-3 w-3 mr-1" />In Progress</Badge>;
case 'Not Started':
return <Badge variant="outline"><XCircle className="h-3 w-3 mr-1" />Not Started</Badge>;
default:
return null;
}
};
const filteredLearners = mockLearners.filter(learner => {
const matchesSearch = learner.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
learner.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || learner.currentItem.status === statusFilter;
const matchesStage = stageFilter === 'all' || learner.stage === stageFilter;
const matchesCohort = cohortFilter === 'all' || learner.cohort === cohortFilter;
return matchesSearch && matchesStatus && matchesStage && matchesCohort;
});
const handleViewLearner = (learner: ProgrammeLearner) => {
setSelectedLearner(learner);
setShowLearnerDrawer(true);
};
const handleExport = async (format: 'excel' | 'csv' | 'pdf') => {
setExporting(true);
await new Promise(resolve => setTimeout(resolve, 2000));
setExporting(false);
console.log(`Exported programme tracker as ${format.toUpperCase()}`);
};
if (loading) {
return (
<div className="space-y-6 animate-pulse">
<div className="h-16 bg-muted rounded-lg"></div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-24 bg-muted rounded-lg"></div>
))}
</div>
<div className="h-96 bg-muted rounded-lg"></div>
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<Card className="sticky top-0 z-20 bg-background border-b shadow-sm">
<CardContent className="pt-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="min-tap-44"
aria-label="Go back to programmes list"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<div className="flex items-center gap-3 mb-1">
<h1 className="text-2xl font-bold">{mockProgramme.title}</h1>
<Badge
variant={mockProgramme.status === 'Active' ? 'default' : 'secondary'}
className={mockProgramme.status === 'Active' ? 'bg-status-success' : ''}
>
{mockProgramme.status}
</Badge>
</div>
<p className="text-muted-foreground">
{mockProgramme.code} {mockProgramme.owner} Version {mockProgramme.version}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={() => onAssignLearners(programmeId)}
className="min-tap-44"
>
<Users className="h-4 w-4 mr-2" />
Assign Learners
</Button>
<Button
variant="outline"
onClick={() => onDownloadTracker(programmeId)}
disabled={exporting}
className="min-tap-44"
>
{exporting ? (
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
) : (
<Download className="h-4 w-4 mr-2" />
)}
Download Tracker
</Button>
<Button
variant="outline"
onClick={() => onOpenAnalytics(programmeId)}
className="min-tap-44"
>
<BarChart3 className="h-4 w-4 mr-2" />
Open Analytics
</Button>
<Dialog>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="min-tap-44"
aria-label="More actions"
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Programme Actions</DialogTitle>
<DialogDescription>Additional actions for this programme</DialogDescription>
</DialogHeader>
<div className="space-y-2">
<Button variant="outline" className="w-full justify-start">
<FileText className="h-4 w-4 mr-2" />
View Structure JSON
</Button>
<Button variant="outline" className="w-full justify-start">
<ExternalLink className="h-4 w-4 mr-2" />
Audit Trail
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</CardContent>
</Card>
{/* Summary Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="pt-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Assignment Window</p>
<p className="font-semibold">{new Date(mockAssignment.startDate).toLocaleDateString()} {new Date(mockAssignment.endDate).toLocaleDateString()}</p>
<p className="text-xs text-muted-foreground">{mockAssignment.orgName}</p>
</div>
<Calendar className="h-8 w-8 text-brand-primary" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Enrolled Learners</p>
<p className="text-2xl font-bold">{mockCounts.learners}</p>
<Button variant="link" className="p-0 h-auto text-xs">Manage</Button>
</div>
<Users className="h-8 w-8 text-brand-primary" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Completion Rate</p>
<p className="text-2xl font-bold">{mockCounts.completionPct}%</p>
<Progress value={mockCounts.completionPct} className="w-16 mt-1" />
</div>
<Award className="h-8 w-8 text-brand-primary" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Items in Programme</p>
<div className="flex gap-2 text-sm">
<span>{mockCounts.courses} Courses</span>
<span></span>
<span>{mockCounts.content} Content</span>
</div>
<div className="flex gap-2 text-sm text-muted-foreground">
<span>{mockCounts.webinars} Webinars</span>
<span></span>
<span>{mockCounts.classes} Classes</span>
</div>
</div>
<BookOpen className="h-8 w-8 text-brand-primary" />
</div>
</CardContent>
</Card>
</div>
{/* Tabs */}
<Card>
<CardContent className="pt-6">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-6">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="structure">Structure</TabsTrigger>
<TabsTrigger value="learners">Learners</TabsTrigger>
<TabsTrigger value="calendar">Calendar</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
{/* Overview Tab */}
<TabsContent value="overview" className="space-y-6 mt-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>Programme Summary</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-medium mb-2">Description</h4>
<p className="text-sm text-muted-foreground">{mockProgramme.description}</p>
</div>
<div>
<h4 className="font-medium mb-2">Learning Goals</h4>
<ul className="text-sm text-muted-foreground space-y-1">
{mockProgramme.goals.map((goal, index) => (
<li key={index} className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-status-success mt-0.5 flex-shrink-0" />
{goal}
</li>
))}
</ul>
</div>
<div>
<h4 className="font-medium mb-2">Tags</h4>
<div className="flex flex-wrap gap-2">
{mockProgramme.tags.map((tag, index) => (
<Badge key={index} variant="outline">{tag}</Badge>
))}
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Stage Breakdown</CardTitle>
<CardDescription>Programme structure as designed by Super Admin</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{[
{ name: 'Pre-assessment', items: mockProgramme.structure.preAssessment, color: 'bg-chart-1' },
{ name: 'Pre-learning', items: mockProgramme.structure.preLearning, color: 'bg-chart-2' },
{ name: 'Classroom sessions', items: mockProgramme.structure.classroom, color: 'bg-chart-3' },
{ name: 'Post-learning', items: mockProgramme.structure.postLearning, color: 'bg-chart-4' }
].map((stage, index) => (
<div key={index} className="border-l-4 border-l-brand-primary pl-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">{stage.name}</h4>
<Badge variant="outline">{stage.items.length} items</Badge>
</div>
<div className="space-y-1">
{stage.items.slice(0, 2).map((item, itemIndex) => (
<div key={itemIndex} className="flex items-center gap-2 text-sm">
{getTypeIcon(item.type)}
<span>{item.title}</span>
{item.status && getStatusBadge(item.status)}
</div>
))}
{stage.items.length > 2 && (
<p className="text-xs text-muted-foreground">
+{stage.items.length - 2} more items
</p>
)}
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
<Alert>
<Bell className="h-4 w-4" />
<AlertDescription>
<strong>Guardrails:</strong> Dates and enrolments shown are scoped to your organization ({mockAssignment.orgName}).
</AlertDescription>
</Alert>
</TabsContent>
{/* Structure Tab */}
<TabsContent value="structure" className="space-y-6 mt-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">Programme Structure</h3>
<p className="text-muted-foreground">Read-only view of the programme as composed by Super Admin</p>
</div>
<Badge variant="outline">Read Only</Badge>
</div>
<Accordion type="single" collapsible className="space-y-2">
{[
{ name: 'Pre-assessment', items: mockProgramme.structure.preAssessment },
{ name: 'Pre-learning', items: mockProgramme.structure.preLearning },
{ name: 'Classroom Sessions (Offline)', items: mockProgramme.structure.classroom },
{ name: 'Post-learning', items: mockProgramme.structure.postLearning }
].map((stage, index) => (
<AccordionItem key={index} value={`stage-${index}`} className="border rounded-lg px-4">
<AccordionTrigger className="hover:no-underline">
<div className="flex items-center gap-3">
<Badge variant="outline">{stage.name}</Badge>
<span className="text-muted-foreground">({stage.items.length} items)</span>
</div>
</AccordionTrigger>
<AccordionContent className="space-y-3 pb-4">
{stage.items.map((item, itemIndex) => (
<div key={itemIndex} className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
<div className="flex items-center gap-3">
{getTypeIcon(item.type)}
<div>
<p className="font-medium">{item.title}</p>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
{item.duration && (
<span className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{item.duration}
</span>
)}
{item.dueDate && (
<span className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
Due: {item.dueDate}
</span>
)}
{item.venue && (
<span className="flex items-center gap-1">
<MapPin className="h-3 w-3" />
{item.venue} {item.room && `${item.room}`}
</span>
)}
{item.date && (
<span className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
{item.date}
</span>
)}
</div>
</div>
</div>
<Button variant="outline" size="sm">
<Eye className="h-4 w-4 mr-2" />
Open
</Button>
</div>
))}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</TabsContent>
{/* Learners Tab */}
<TabsContent value="learners" className="space-y-6 mt-6">
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
<div>
<h3 className="text-lg font-semibold">Learner Progress</h3>
<p className="text-muted-foreground">Track individual progress across the programme</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={() => handleExport('excel')}
disabled={exporting}
className="min-tap-44"
>
{exporting ? (
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
) : (
<Download className="h-4 w-4 mr-2" />
)}
Export Data
</Button>
</div>
</div>
{/* Filters */}
<div className="flex flex-wrap gap-4 items-center">
<div className="relative flex-1 min-w-[200px] max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search learners..."
className="pl-10"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="Not Started">Not Started</SelectItem>
<SelectItem value="In-Progress">In Progress</SelectItem>
<SelectItem value="Completed">Completed</SelectItem>
</SelectContent>
</Select>
<Select value={stageFilter} onValueChange={setStageFilter}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Stage" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Stages</SelectItem>
<SelectItem value="Pre-assessment">Pre-assessment</SelectItem>
<SelectItem value="Pre-learning">Pre-learning</SelectItem>
<SelectItem value="Classroom">Classroom</SelectItem>
<SelectItem value="Post-learning">Post-learning</SelectItem>
</SelectContent>
</Select>
<Select value={cohortFilter} onValueChange={setCohortFilter}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Cohort" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Cohorts</SelectItem>
<SelectItem value="Cohort A">Cohort A</SelectItem>
<SelectItem value="Cohort B">Cohort B</SelectItem>
</SelectContent>
</Select>
</div>
{/* Learners Table */}
<div className="rounded-md border">
<Table>
<TableHeader className="sticky-header">
<TableRow>
<TableHead className="w-[200px]">Learner</TableHead>
<TableHead className="w-[200px]">Current Item</TableHead>
<TableHead className="w-[100px]">Progress</TableHead>
<TableHead className="w-[150px]">Next Item</TableHead>
<TableHead className="w-[120px]">Last Activity</TableHead>
<TableHead className="w-[100px]">Stage</TableHead>
<TableHead className="w-[80px]">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredLearners.map((learner) => (
<TableRow key={learner.id} className="min-h-[48px]">
<TableCell>
<div>
<p className="font-medium">{learner.name}</p>
<p className="text-sm text-muted-foreground">{learner.email}</p>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
{getTypeIcon(learner.currentItem.type)}
<div>
<p className="font-medium text-sm">{learner.currentItem.title}</p>
{getStatusBadge(learner.currentItem.status)}
</div>
</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<Progress value={learner.progressPct} className="w-16" />
<span className="text-sm">{learner.progressPct}%</span>
</div>
</TableCell>
<TableCell>
{learner.nextItem ? (
<div className="flex items-center gap-2">
{getTypeIcon(learner.nextItem.type)}
<span className="text-sm">{learner.nextItem.title}</span>
</div>
) : (
<span className="text-sm text-muted-foreground">None</span>
)}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{learner.lastActivity}
</TableCell>
<TableCell>
<Badge variant="outline" className="text-xs">
{learner.stage}
</Badge>
</TableCell>
<TableCell>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleViewLearner(learner)}
className="min-tap-44"
aria-label={`View details for ${learner.name}`}
>
<Eye className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
className="min-tap-44"
aria-label={`Send reminder to ${learner.name}`}
>
<Mail className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</TabsContent>
{/* Calendar Tab */}
<TabsContent value="calendar" className="space-y-6 mt-6">
<div className="text-center py-12 text-muted-foreground">
<Calendar className="h-12 w-12 mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">Programme Calendar</h3>
<p>Programme-scoped calendar with webinars, offline classes, and deadlines would be displayed here</p>
</div>
</TabsContent>
{/* Reports Tab */}
<TabsContent value="reports" className="space-y-6 mt-6">
<div className="text-center py-12 text-muted-foreground">
<BarChart3 className="h-12 w-12 mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">Programme Reports</h3>
<p>Detailed analytics and reporting for this programme would be displayed here</p>
</div>
</TabsContent>
{/* Activity Tab */}
<TabsContent value="activity" className="space-y-6 mt-6">
<div className="text-center py-12 text-muted-foreground">
<Activity className="h-12 w-12 mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">Activity Log</h3>
<p>Programme activity audit trail would be displayed here</p>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* Learner Details Drawer */}
<Sheet open={showLearnerDrawer} onOpenChange={setShowLearnerDrawer}>
<SheetContent className="w-[480px] sm:w-[540px]">
<SheetHeader>
<SheetTitle>{selectedLearner?.name}</SheetTitle>
<SheetDescription>Learner progress and assignment details</SheetDescription>
</SheetHeader>
{selectedLearner && (
<div className="space-y-6 mt-6">
<div>
<h4 className="font-medium mb-2">Assignment Info</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Start Date:</span>
<span>{mockAssignment.startDate}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">End Date:</span>
<span>{mockAssignment.endDate}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Cohort:</span>
<span>{selectedLearner.cohort}</span>
</div>
</div>
</div>
<div>
<h4 className="font-medium mb-2">Stage Timeline</h4>
<div className="space-y-3">
{[
{ name: 'Pre-assessment', completed: selectedLearner.stage !== 'Pre-assessment' },
{ name: 'Pre-learning', completed: !['Pre-assessment', 'Pre-learning'].includes(selectedLearner.stage) },
{ name: 'Classroom', completed: !['Pre-assessment', 'Pre-learning', 'Classroom'].includes(selectedLearner.stage) },
{ name: 'Post-learning', completed: false }
].map((stage, index) => (
<div key={index} className="flex items-center gap-3">
<div className={`w-4 h-4 rounded-full border-2 ${
stage.completed ? 'bg-status-success border-status-success' :
selectedLearner.stage === stage.name ? 'border-brand-primary bg-brand-primary' :
'border-muted'
}`}>
{stage.completed && <CheckCircle className="w-4 h-4 text-white" />}
</div>
<span className={`text-sm ${
selectedLearner.stage === stage.name ? 'font-medium' : 'text-muted-foreground'
}`}>
{stage.name}
</span>
</div>
))}
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1">
<Eye className="h-4 w-4 mr-2" />
Open Learner Record
</Button>
<Button variant="outline" className="flex-1">
<Mail className="h-4 w-4 mr-2" />
Send Reminder
</Button>
</div>
</div>
)}
</SheetContent>
</Sheet>
</div>
);
};