diff --git a/src/App.tsx b/src/App.tsx
index ac5b9ee..7360e3d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,14 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Button } from './components/ui/button';
+import { ProgrammesTable } from './components/ProgrammesTable';
+import { ProgrammeSchedule } from './components/ProgrammeSchedule';
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from './components/ui/dropdown-menu';
+import klcLogo from './assets/klc-logo.png';
+import { ProgrammeCalendar } from './components/ProgrammeCalendar';
+import { LearningAnalyticsTable } from './components/LearningAnalyticsTable';
+import { DiscussionForumFeed } from './components/DiscussionForumFeed';
+import { ProgrammeHRView } from './components/ProgrammeHRView';
+import { CourseHRView } from './components/CourseHRView';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './components/ui/card';
import { Badge } from './components/ui/badge';
import { Input } from './components/ui/input';
@@ -14,7 +23,7 @@ import { Skeleton } from './components/ui/skeleton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs';
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from './components/ui/breadcrumb';
import { Alert, AlertDescription } from './components/ui/alert';
-import logo from '../src/assets/klc-logo.png';
+import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PaginationEllipsis } from './components/ui/pagination';
import {
Home,
Users,
@@ -52,8 +61,27 @@ import {
CreditCard,
Shield,
ExternalLink,
- Info
+ Info,
+ LogOut,
+ User,
+ Repeat,
+ Users2,
+ Pin,
+ Heart,
+ MessageCircle,
+ Share2,
+ Flag,
+ Reply,
+ Hash,
+ Send,
+ Image,
+ Bold,
+ Italic,
+ Link,
+ List,
+ Smile
} from 'lucide-react';
+import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from 'recharts';
// Types
interface KPIData {
@@ -92,6 +120,48 @@ interface Deadline {
dueTime: string;
}
+interface Cohort {
+ id: string;
+ name: string;
+ memberCount: number;
+ programme?: string;
+ isActive: boolean;
+}
+
+interface Thread {
+ id: string;
+ title: string;
+ content: string;
+ author: {
+ id: string;
+ name: string;
+ avatar?: string;
+ };
+ cohortId: string;
+ createdAt: string;
+ lastActivity: string;
+ replyCount: number;
+ isPinned?: boolean;
+ tags?: string[];
+ reactions?: { [key: string]: string[] }; // emoji -> user IDs
+}
+
+interface Post {
+ id: string;
+ threadId: string;
+ content: string;
+ author: {
+ id: string;
+ name: string;
+ avatar?: string;
+ };
+ createdAt: string;
+ editedAt?: string;
+ reactions?: { [key: string]: string[] };
+ isReported?: boolean;
+ parentId?: string; // for nested replies
+}
+
interface TestimonialFormData {
name: string;
email: string;
@@ -102,6 +172,21 @@ interface TestimonialFormData {
consentToPublish: boolean;
}
+// Helper function to determine user access level
+// This would typically be determined by user roles/permissions from your backend
+const getUserAccessLevel = (): 'full' | 'course-only' => {
+ // Mock logic: For demonstration, we'll simulate that users with email containing 'course-only'
+ // have course-only access. In real implementation, this would check user roles/permissions.
+
+ // For testing purposes, you can change this return value:
+ // - 'full': Shows programmes with "Programme/Course" column header and programme data
+ // - 'course-only': Shows only courses with "Course" column header and course data
+
+ // TODO: Replace with actual user role/permission check from your backend
+ // Example: return userRole === 'hr-course-only' ? 'course-only' : 'full';
+ return 'full'; // Default to full access
+};
+
// Custom hooks
const useLocalStorage = (key: string, initialValue: any) => {
const [storedValue, setStoredValue] = useState(() => {
@@ -182,6 +267,118 @@ const mockDeadlines: Deadline[] = [
{ id: '5', title: 'Team Building Session', type: 'webinar', dueDate: 'Jan 5', dueTime: '3:30 PM' }
];
+const mockCohorts: Cohort[] = [
+ { id: '1', name: 'Leadership Development Q4 2024', memberCount: 30, programme: 'Leadership Development', isActive: true },
+ { id: '2', name: 'Technical Skills Cohort A', memberCount: 25, programme: 'Technical Skills', isActive: true },
+ { id: '3', name: 'Communication Workshop Group', memberCount: 18, programme: 'Communication', isActive: true },
+ { id: '4', name: 'Project Management Certification', memberCount: 22, programme: 'Project Management', isActive: false }
+];
+
+const mockThreads: Thread[] = [
+ {
+ id: '1',
+ title: 'Best practices for team communication during remote work',
+ content: 'What strategies have you found most effective for maintaining clear communication with remote team members? I\'d love to hear about tools and techniques that have worked well for your teams.',
+ author: { id: 'user1', name: 'Sarah Chen' },
+ cohortId: '1',
+ createdAt: '2024-12-28T10:30:00Z',
+ lastActivity: '2024-12-28T15:45:00Z',
+ replyCount: 12,
+ isPinned: true,
+ tags: ['communication', 'remote-work', 'best-practices'],
+ reactions: { '👍': ['user2', 'user3'], '💡': ['user4'] }
+ },
+ {
+ id: '2',
+ title: 'How to handle difficult conversations with team members?',
+ content: 'I\'m struggling with addressing performance issues with one of my team members. Any advice on how to approach this sensitively while being direct about expectations?',
+ author: { id: 'user2', name: 'Michael Rodriguez' },
+ cohortId: '1',
+ createdAt: '2024-12-28T09:15:00Z',
+ lastActivity: '2024-12-28T14:20:00Z',
+ replyCount: 8,
+ tags: ['difficult-conversations', 'performance-management'],
+ reactions: { '🤔': ['user1', 'user5'], '💪': ['user3'] }
+ },
+ {
+ id: '3',
+ title: 'Share your leadership book recommendations',
+ content: 'What books have been most influential in your leadership journey? Looking for practical reads that offer actionable insights.',
+ author: { id: 'user3', name: 'Emma Thompson' },
+ cohortId: '1',
+ createdAt: '2024-12-27T16:00:00Z',
+ lastActivity: '2024-12-28T11:30:00Z',
+ replyCount: 15,
+ tags: ['books', 'recommendations', 'learning'],
+ reactions: { '📚': ['user1', 'user2', 'user4', 'user5'], '⭐': ['user6'] }
+ },
+ {
+ id: '4',
+ title: 'Question about the delegation framework from Module 3',
+ content: 'Can someone clarify the difference between the delegation levels we covered? I want to make sure I\'m applying them correctly in my current projects.',
+ author: { id: 'user4', name: 'David Kim' },
+ cohortId: '1',
+ createdAt: '2024-12-27T14:30:00Z',
+ lastActivity: '2024-12-27T18:45:00Z',
+ replyCount: 6,
+ tags: ['module-3', 'delegation', 'clarification'],
+ reactions: { '❓': ['user2'], '👍': ['user1'] }
+ }
+];
+
+const mockPosts: Post[] = [
+ {
+ id: '1',
+ threadId: '1',
+ content: 'Great question! I\'ve found that establishing clear communication protocols at the start of projects makes a huge difference. We use a combination of daily stand-ups via video call and async updates through Slack.',
+ author: { id: 'user5', name: 'Lisa Wang' },
+ createdAt: '2024-12-28T11:00:00Z',
+ reactions: { '👍': ['user1', 'user2'], '💯': ['user3'] }
+ },
+ {
+ id: '2',
+ threadId: '1',
+ content: 'One thing that\'s worked well for our team is having "communication preferences" documented for each team member. Some prefer quick calls for complex topics, others prefer detailed written explanations. Knowing this upfront prevents a lot of miscommunication.',
+ author: { id: 'user6', name: 'Robert Lee' },
+ createdAt: '2024-12-28T12:15:00Z',
+ reactions: { '💡': ['user1', 'user4'], '👏': ['user2'] }
+ },
+ {
+ id: '3',
+ threadId: '1',
+ content: '@Sarah Chen Thanks for starting this discussion! I\'d add that regular one-on-ones have been crucial for me. Even in remote settings, that personal connection makes a big difference in team dynamics.',
+ author: { id: 'user7', name: 'Jennifer Davis' },
+ createdAt: '2024-12-28T13:30:00Z',
+ reactions: { '🎯': ['user1'], '👍': ['user5'] }
+ }
+];
+
+// Chart data for reports
+const completionTrendsData = [
+ { week: 'Week 1', completed: 12, started: 25, completionRate: 48 },
+ { week: 'Week 2', completed: 18, started: 30, completionRate: 60 },
+ { week: 'Week 3', completed: 22, started: 35, completionRate: 63 },
+ { week: 'Week 4', completed: 28, started: 40, completionRate: 70 },
+ { week: 'Week 5', completed: 35, started: 45, completionRate: 78 },
+ { week: 'Week 6', completed: 42, started: 50, completionRate: 84 },
+ { week: 'Week 7', completed: 38, started: 48, completionRate: 79 },
+ { week: 'Week 8', completed: 45, started: 52, completionRate: 87 },
+ { week: 'Week 9', completed: 48, started: 55, completionRate: 87 },
+ { week: 'Week 10', completed: 52, started: 58, completionRate: 90 },
+ { week: 'Week 11', completed: 55, started: 60, completionRate: 92 },
+ { week: 'Week 12', completed: 58, started: 62, completionRate: 94 }
+];
+
+const activityHeatmapData = [
+ { day: 'Mon', '6AM': 2, '9AM': 15, '12PM': 25, '3PM': 35, '6PM': 20, '9PM': 8 },
+ { day: 'Tue', '6AM': 3, '9AM': 18, '12PM': 30, '3PM': 38, '6PM': 22, '9PM': 10 },
+ { day: 'Wed', '6AM': 5, '9AM': 22, '12PM': 28, '3PM': 42, '6PM': 25, '9PM': 12 },
+ { day: 'Thu', '6AM': 4, '9AM': 20, '12PM': 32, '3PM': 40, '6PM': 28, '9PM': 15 },
+ { day: 'Fri', '6AM': 6, '9AM': 25, '12PM': 35, '3PM': 30, '6PM': 18, '9PM': 8 },
+ { day: 'Sat', '6AM': 8, '9AM': 12, '12PM': 20, '3PM': 25, '6PM': 30, '9PM': 22 },
+ { day: 'Sun', '6AM': 10, '9AM': 8, '12PM': 15, '3PM': 18, '6PM': 28, '9PM': 25 }
+];
+
// Components
const KPICard: React.FC<{ data: KPIData; onClick?: () => void; className?: string }> = ({ data, onClick, className = '' }) => {
const countedValue = useCountUp(data.value);
@@ -221,14 +418,97 @@ const KPICard: React.FC<{ data: KPIData; onClick?: () => void; className?: strin
);
};
+const EmployeeProgressCard: React.FC<{
+ employee: Employee;
+ onEdit?: (employee: Employee) => void;
+}> = ({ employee, onEdit }) => {
+ const getProgressColor = (progress: number) => {
+ if (progress >= 80) return 'bg-status-success';
+ if (progress >= 60) return 'bg-status-warn';
+ return 'bg-status-error';
+ };
+
+ const getProgressStatus = (progress: number) => {
+ if (progress === 0) return 'Not Started';
+ if (progress >= 80) return 'Excellent';
+ if (progress >= 60) return 'Good';
+ return 'Needs Attention';
+ };
+
+ return (
+ onEdit?.(employee)}>
+
+
+
+
+ {employee.name.split(' ').map(n => n[0]).join('')}
+
+
+
{employee.name}
+
+ {employee.status}
+
+
+
+
{ e.stopPropagation(); onEdit?.(employee); }}>
+
+
+
+
+ {employee.programme && (
+
+
+
+
{employee.programme}
+
{employee.course}
+
+
{employee.progress}%
+
+
+
+
+
+
+ {getProgressStatus(employee.progress || 0)}
+
+ {employee.lastActivity}
+
+
+
+ )}
+
+
+ );
+};
+
const EmployeeTable: React.FC<{
employees: Employee[];
onEdit?: (employee: Employee) => void;
showProgress?: boolean;
maxHeight?: string;
-}> = ({ employees, onEdit, showProgress = true, maxHeight = "400px" }) => {
+ viewMode?: 'table' | 'cards';
+}> = ({ employees, onEdit, showProgress = true, maxHeight = "400px", viewMode = 'table' }) => {
+ if (viewMode === 'cards') {
+ return (
+
+ {employees.map((employee) => (
+
+ ))}
+
+ );
+ }
+
return (
-
+
@@ -336,9 +616,8 @@ const HRSidebar: React.FC<{
const menuItems = [
{ id: 'home', label: 'Dashboard', icon: Home, path: '/hr/home' },
{ id: 'learners', label: 'Learners', icon: Users, path: '/hr/learners' },
- { id: 'analytics', label: 'Analytics', icon: BarChart3, path: '/hr/analytics' },
- { id: 'testimonials', label: 'Testimonials', icon: MessageSquare, path: '/hr/testimonials' },
- { id: 'settings', label: 'Settings', icon: Settings, path: '/hr/settings' }
+ { id: 'reports', label: 'Reports', icon: BarChart3, path: '/hr/reports' },
+ { id: 'discussions', label: 'Discussion Forums', icon: MessageSquare, path: '/hr/discussions' }
];
return (
@@ -346,9 +625,9 @@ const HRSidebar: React.FC<{
- AC
+ TS
-
Acme Corp
+
Tech Solutions Pvt Ltd
@@ -389,7 +668,9 @@ const HRSidebar: React.FC<{
const TopNav: React.FC<{
onMenuToggle?: () => void;
showMenuButton?: boolean;
-}> = ({ onMenuToggle, showMenuButton = false }) => {
+ onNotificationToggle?: () => void;
+ notificationCount?: number;
+}> = ({ onMenuToggle, showMenuButton = false, onNotificationToggle, notificationCount = 0 }) => {
return (
@@ -404,35 +685,130 @@ const TopNav: React.FC<{
)}
-
-
+
+
-
+
+ {notificationCount > 0 && (
+
+ {notificationCount > 9 ? '9+' : notificationCount}
+
+ )}
-
- HR
-
+
+ {/* HR Profile Dropdown */}
+
+
+
+ P
+
+
+
+ {/* User Profile Section */}
+
+
+ P
+
+
+
Priya
+
Tech Solutions Pvt Ltd
+
+
+
+ {/* Switch Mode Section */}
+
+
• Switch Mode
+
+
+
+ Learning
+
+
+
+ HR Mode
+
+
+
+
+ {/* Switch Accounts Section */}
+
+
• Switch Accounts
+
+
+
+ Corporate
+
+
+
+ Personal
+
+
+
+
+
+
+ {/* Profile & Settings */}
+
+
+
+
Profile & Settings
+
Manage your account
+
+
+
+ {/* Sign out */}
+
+
+
+
Sign out
+
End your session
+
+
+
+
);
};
-const BreadcrumbNav: React.FC<{ currentScreen: string }> = ({ currentScreen }) => {
+const BreadcrumbNav: React.FC<{ currentScreen: string; currentProgrammeId?: string | null; currentCourseId?: string | null }> = ({ currentScreen, currentProgrammeId, currentCourseId }) => {
const getBreadcrumbText = (screen: string) => {
switch (screen) {
- case 'home': return 'HR Home';
+ case 'home': return 'Dashboard';
case 'learners': return 'Learners';
- case 'analytics': return 'Analytics & Reports';
- case 'settings': return 'HR Settings';
- case 'testimonials': return 'Testimonials';
+ case 'discussions': return 'Discussion Forums';
+ case 'reports': return 'Reports';
+ case 'profile': return 'Profile';
+ case 'programme-view': return 'Programme Details';
+ case 'course-view': return 'Course Details';
default: return 'HR Portal';
}
};
@@ -452,11 +828,83 @@ const BreadcrumbNav: React.FC<{ currentScreen: string }> = ({ currentScreen }) =
);
};
+const AnnouncementsPanel: React.FC<{
+ isOpen: boolean;
+ onClose: () => void;
+ announcements: Announcement[];
+ onMarkAsRead?: (id: string) => void;
+}> = ({ isOpen, onClose, announcements, onMarkAsRead }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+
Announcements & Reminders
+
+
+
+
+
+
+ {announcements.map((item) => (
+
onMarkAsRead?.(item.id)}
+ >
+
+
+
+ {item.type === 'announcement' ?
+ :
+
+ }
+
+ {item.pinned && (
+
Pinned
+ )}
+
+
{item.timestamp}
+
+
+
{item.title}
+
{item.content}
+
+
+
+ {item.type}
+
+
+ Mark as read
+
+
+
+ ))}
+
+
+
+
+ View All Notifications
+
+
+
+ );
+};
+
const ChatBot: React.FC<{ currentScreen?: string }> = ({ currentScreen }) => {
const [isOpen, setIsOpen] = useState(false);
const getChipsForScreen = (screen?: string) => {
- if (screen === 'testimonials') {
+ if (screen === 'profile') {
return [
"How do I submit a testimonial?",
"When will my testimonial be reviewed?",
@@ -475,7 +923,7 @@ const ChatBot: React.FC<{ currentScreen?: string }> = ({ currentScreen }) => {
const chips = getChipsForScreen(currentScreen);
return (
-
+
{isOpen && (
@@ -515,40 +963,19 @@ const ChatBot: React.FC<{ currentScreen?: string }> = ({ currentScreen }) => {
};
// Screen Components
-const HRHomeScreen: React.FC<{ onNavigate: (screen: string, filters?: any) => void }> = ({ onNavigate }) => {
+const HRHomeScreen: React.FC<{
+ onNavigate: (screen: string, filters?: any) => void;
+ onViewProgramme: (programmeId: string) => void;
+ onViewCourse: (courseId: string) => void;
+}> = ({ onNavigate, onViewProgramme, onViewCourse }) => {
const [loading, setLoading] = useState(true);
const [prefersReducedMotion] = useLocalStorage('prefersReducedMotion', false);
useEffect(() => {
- const timer = setTimeout(() => setLoading(false), 1000);
+ const timer = setTimeout(() => setLoading(false), 800);
return () => clearTimeout(timer);
}, []);
- const handleKPIClick = (kpiTitle: string) => {
- let filters = {};
- switch (kpiTitle) {
- case 'Total Learners':
- filters = { status: 'all' };
- break;
- case 'Active Courses':
- filters = { status: 'active' };
- break;
- case 'Completed Profilers':
- filters = { completed: true };
- break;
- default:
- filters = {};
- }
- onNavigate('learners', filters);
- };
-
- const cohortData = [
- { name: 'Leadership Development', notStarted: 15, inProgress: 28, completed: 42 },
- { name: 'Technical Skills', notStarted: 22, inProgress: 35, completed: 38 },
- { name: 'Communication', notStarted: 18, inProgress: 24, completed: 31 },
- { name: 'Project Management', notStarted: 12, inProgress: 19, completed: 28 }
- ];
-
if (loading) {
return (
@@ -581,188 +1008,24 @@ const HRHomeScreen: React.FC<{ onNavigate: (screen: string, filters?: any) => vo
{/* Welcome Section */}
-
Hello HR Pooja 👋
-
See what's happening today at Acme Corp
+
Welcome Priya 👋
+
Manage programmes, track progress, and stay connected with your learning community
- {/* KPI Cards */}
-
- {mockKPIData.map((kpi, index) => (
- handleKPIClick(kpi.title)}
- className={prefersReducedMotion ? '' : 'animate-fade-in'}
- style={{ animationDelay: prefersReducedMotion ? '0ms' : `${index * 100 + 200}ms` }}
- />
- ))}
-
-
- {/* Employee Assignment & Progress */}
-
-
-
-
- Employee Assignment & Progress
- Snapshot of current learning activities
-
-
-
-
-
-
-
- All Programmes
- Leadership Development
- Technical Skills
- Communication
-
-
- onNavigate('analytics')}
- className="min-tap-44"
- >
-
- View all in Analytics
-
-
-
- Download CSV
-
-
-
-
-
- onNavigate('learners', { editEmployee: employee.id })}
- maxHeight="360px"
- />
-
-
-
-
- {/* Cohort Progress Chart */}
-
-
-
-
- Cohort Progress
- Progress overview by programme
-
-
-
- Auto-refresh
-
-
-
-
-
-
- Stacked bar chart showing progress across different learning programmes.
- Each bar represents not started, in progress, and completed learners.
-
- {cohortData.map((cohort, index) => {
- const total = cohort.notStarted + cohort.inProgress + cohort.completed;
- const notStartedPercent = (cohort.notStarted / total) * 100;
- const inProgressPercent = (cohort.inProgress / total) * 100;
- const completedPercent = (cohort.completed / total) * 100;
-
- return (
-
-
- {cohort.name}
- {total} learners
-
-
-
- Not Started: {cohort.notStarted}
- In Progress: {cohort.inProgress}
- Completed: {cohort.completed}
-
-
- );
- })}
-
-
-
-
- {/* Upcoming Deadlines */}
-
-
- Upcoming Deadlines
- Next 7 days
-
-
-
- {mockDeadlines.map((deadline) => (
-
-
-
- {deadline.type === 'webinar' ?
- :
-
- }
-
-
-
{deadline.title}
-
{deadline.type}
-
-
-
-
- {deadline.dueDate}
-
-
{deadline.dueTime}
-
-
- ))}
-
-
-
-
-
- {/* Quick Links */}
-
+ {/* Quick Actions Section - Unchanged */}
+
Quick Actions
- Common HR tasks
+ Common HR tasks and shortcuts
{[
{ title: 'Add Learners', icon: Plus, action: () => onNavigate('learners', { action: 'add' }) },
- { title: 'Assign Courses', icon: BookOpen, action: () => onNavigate('learners', { action: 'assign' }) },
- { title: 'Download Reports', icon: Download, action: () => onNavigate('analytics') },
- { title: 'Testimonials Queue', icon: MessageSquare, action: () => onNavigate('testimonials') }
+ { title: 'Assign', icon: BookOpen, action: () => onNavigate('learners', { action: 'assign' }) },
+ { title: 'Download Reports', icon: Download, action: () => onNavigate('reports') },
+ { title: 'Submit Testimonial', icon: MessageSquare, action: () => onNavigate('profile') }
].map((link, index) => {
const Icon = link.icon;
return (
@@ -775,7 +1038,6 @@ const HRHomeScreen: React.FC<{ onNavigate: (screen: string, filters?: any) => vo
${prefersReducedMotion ? '' : 'animate-scale-hover'}
`}
aria-label={link.title}
- aria-controls={link.title === 'Add Learners' ? 'learners-screen' : undefined}
>
{link.title}
@@ -786,65 +1048,40 @@ const HRHomeScreen: React.FC<{ onNavigate: (screen: string, filters?: any) => vo
- {/* Announcements & Reminders */}
-
-
-
-
- Announcements & Reminders
- Recent updates and notifications
-
-
-
-
-
-
- All
- Announcements
- Reminders
-
-
-
-
-
-
- {mockAnnouncements.map((item) => (
-
-
-
-
-
{item.title}
- {item.pinned && (
- Pinned
- )}
-
-
{item.content}
-
-
- {item.type}
-
- {item.timestamp}
-
-
-
-
-
-
-
- ))}
-
-
-
+ {/* Programmes Table */}
+
+
console.log(`Assign learners to: ${programmeId}`)}
+ onDownloadTracker={(programmeId) => console.log(`Download tracker for: ${programmeId}`)}
+ userAccessLevel={getUserAccessLevel()}
+ />
+
+
+ {/* Programme Schedule - Horizontal layout below programmes */}
+
+
console.log(`Open event: ${event.title}`)}
+ />
+
+
+ {/* Learning Analytics - Full Width */}
+
+ onNavigate('learners', { editEmployee: learnerId })}
+ onNudgeLearner={(learnerId) => console.log(`Nudge learner: ${learnerId}`)}
+ onViewAllAnalytics={(programmeId) => onNavigate('reports', { programme: programmeId })}
+ />
+
+
+ {/* Discussion Forum Feed */}
+
+ console.log(`Open thread: ${threadId}`)}
+ onMarkAsRead={(threadId) => console.log(`Mark as read: ${threadId}`)}
+ />
+
);
};
@@ -861,6 +1098,8 @@ const LearnersScreen: React.FC<{ filters?: any }> = ({ filters }) => {
const [editingEmployee, setEditingEmployee] = useState(null);
const [newEmployee, setNewEmployee] = useState({ name: '', email: '', phone: '' });
const [bulkActionVisible, setBulkActionVisible] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPage, setItemsPerPage] = useState(10);
const debouncedSearch = useCallback(
(term: string) => {
@@ -880,6 +1119,17 @@ const LearnersScreen: React.FC<{ filters?: any }> = ({ filters }) => {
return matchesSearch && matchesStatus;
});
+ // Pagination calculations
+ const totalPages = Math.ceil(filteredEmployees.length / itemsPerPage);
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ const paginatedEmployees = filteredEmployees.slice(startIndex, endIndex);
+
+ // Reset to first page when filters change
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [searchTerm, statusFilter]);
+
const handleEmployeeSelect = (employeeId: string, selected: boolean) => {
if (selected) {
setSelectedEmployees(prev => [...prev, employeeId]);
@@ -890,12 +1140,17 @@ const LearnersScreen: React.FC<{ filters?: any }> = ({ filters }) => {
const handleBulkSelect = (selectAll: boolean) => {
if (selectAll) {
- setSelectedEmployees(filteredEmployees.map(emp => emp.id));
+ setSelectedEmployees(paginatedEmployees.map(emp => emp.id));
} else {
setSelectedEmployees([]);
}
};
+ const handlePageChange = (page: number) => {
+ setCurrentPage(page);
+ setSelectedEmployees([]); // Clear selections when changing pages
+ };
+
useEffect(() => {
setBulkActionVisible(selectedEmployees.length > 0);
}, [selectedEmployees]);
@@ -979,7 +1234,7 @@ const LearnersScreen: React.FC<{ filters?: any }> = ({ filters }) => {
- {selectedEmployees.length} learner{selectedEmployees.length !== 1 ? 's' : ''} selected
+ {selectedEmployees.length} learner{selectedEmployees.length !== 1 ? 's' : ''} selected {paginatedEmployees.length > 0 && selectedEmployees.length === paginatedEmployees.length ? '(current page)' : ''}
+
+ {/* Pagination */}
+ {totalPages > 1 && (
+
+
+ Page {currentPage} of {totalPages}
+
+
+
+
+ handlePageChange(Math.max(1, currentPage - 1))}
+ className={currentPage === 1 ? 'pointer-events-none opacity-50' : 'cursor-pointer'}
+ />
+
+
+ {/* Show page numbers */}
+ {(() => {
+ const pages = [];
+ const maxVisiblePages = 5;
+ let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
+ let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
+
+ // Adjust startPage if we're near the end
+ if (endPage - startPage + 1 < maxVisiblePages) {
+ startPage = Math.max(1, endPage - maxVisiblePages + 1);
+ }
+
+ // Show first page if not in range
+ if (startPage > 1) {
+ pages.push(
+
+ handlePageChange(1)}
+ isActive={currentPage === 1}
+ className="cursor-pointer"
+ >
+ 1
+
+
+ );
+ if (startPage > 2) {
+ pages.push(
+
+
+
+ );
+ }
+ }
+
+ // Show page range
+ for (let i = startPage; i <= endPage; i++) {
+ pages.push(
+
+ handlePageChange(i)}
+ isActive={currentPage === i}
+ className="cursor-pointer"
+ >
+ {i}
+
+
+ );
+ }
+
+ // Show last page if not in range
+ if (endPage < totalPages) {
+ if (endPage < totalPages - 1) {
+ pages.push(
+
+
+
+ );
+ }
+ pages.push(
+
+ handlePageChange(totalPages)}
+ isActive={currentPage === totalPages}
+ className="cursor-pointer"
+ >
+ {totalPages}
+
+
+ );
+ }
+
+ return pages;
+ })()}
+
+
+ handlePageChange(Math.min(totalPages, currentPage + 1))}
+ className={currentPage === totalPages ? 'pointer-events-none opacity-50' : 'cursor-pointer'}
+ />
+
+
+
+
+ )}
{/* Add Learner Drawer */}
= ({ filters }) => {
Add a new learner to the system. Email cannot be changed after saving.
-
+
-
+
Employee Name *
= ({ filters }) => {
/>
-
+
Email Address *
= ({ filters }) => {
/>
-
+
Phone Number
= ({ filters }) => {
);
};
-const AnalyticsScreen: React.FC<{ filters?: any }> = ({ filters }) => {
- const [dateRange, setDateRange] = useState('last-30-days');
- const [selectedProgrammes, setSelectedProgrammes] = useState(['all']);
- const [loading, setLoading] = useState(false);
- const [exporting, setExporting] = useState(false);
- const analyticsKPIData: KPIData[] = [
- { title: 'Total Learners', value: 1247, change: 8.2, trend: 'up' },
- { title: 'New Enrolments', value: 89, change: 15.3, trend: 'up' },
- { title: 'Course Completions', value: 342, change: -2.1, trend: 'down' },
- { title: 'Assessment Rates', value: 78, change: 5.7, trend: 'up' }
- ];
- const handleExport = async (format: 'csv' | 'pdf') => {
- setExporting(true);
- // Simulate export process
- await new Promise(resolve => setTimeout(resolve, 2000));
- setExporting(false);
- console.log(`Exported as ${format.toUpperCase()}`);
- };
-
- const handleRunReport = () => {
- setLoading(true);
- // Simulate report generation
- setTimeout(() => setLoading(false), 1500);
- };
-
- const chartData = [
- { month: 'Jan', enrolments: 45, completions: 38, assessments: 42 },
- { month: 'Feb', enrolments: 52, completions: 41, assessments: 38 },
- { month: 'Mar', enrolments: 48, completions: 44, assessments: 46 },
- { month: 'Apr', enrolments: 61, completions: 49, assessments: 52 },
- { month: 'May', enrolments: 55, completions: 52, assessments: 48 },
- { month: 'Jun', enrolments: 67, completions: 58, assessments: 61 }
- ];
-
- return (
-
- {/* Filter Bar */}
-
-
-
-
-
-
- Date Range
-
-
-
-
-
-
- Last 7 days
- Last 30 days
- Last 90 days
- Custom range
-
-
-
-
-
- Programmes
-
-
-
-
-
-
- All Programmes
- Leadership Development
- Technical Skills
- Communication
-
-
-
-
-
- {loading && }
- Run Report
-
-
-
-
-
- {/* KPI Cards */}
-
- {analyticsKPIData.map((kpi, index) => (
-
- ))}
-
-
- {/* Charts Panel */}
-
-
-
-
- Learning Analytics Overview
- Key metrics over time
-
-
- Last refreshed: 10 minutes ago
-
-
-
-
-
-
-
-
Interactive chart would be rendered here
-
- Line/Bar chart showing enrolments, completions, and assessments over time
-
-
-
-
- Line and bar chart showing learning analytics over the selected time period.
- Displays new enrolments, course completions, and assessment completion rates.
- Chart includes interactive legend for toggling data series visibility.
-
-
-
-
- {/* Detailed Table */}
-
-
-
-
- Assignments & Progress Detail
- Complete learner progress breakdown
-
-
- handleExport('csv')}
- disabled={exporting}
- className="min-tap-44"
- aria-live="polite"
- >
- {exporting ? (
-
- ) : (
-
- )}
- Export CSV
-
- handleExport('pdf')}
- disabled={exporting}
- className="min-tap-44"
- aria-live="polite"
- >
- {exporting ? (
-
- ) : (
-
- )}
- Export PDF
-
-
-
-
-
-
-
-
-
- {/* Data Freshness Note */}
-
-
- Last refreshed: {new Date().toLocaleTimeString()} •
- Next refresh in 4 minutes
-
-
-
- );
-};
-
-const TestimonialsScreen: React.FC = () => {
+const TestimonialsTabContent: React.FC = () => {
const [loading, setLoading] = useState(true);
const [formData, setFormData] = useState({
name: 'Alex Sharma',
@@ -1721,7 +1901,7 @@ const TestimonialsScreen: React.FC = () => {
)}
{/* Testimonial Form */}
-
+
Submit Testimonial
@@ -1729,7 +1909,7 @@ const TestimonialsScreen: React.FC = () => {
-