From 399b860077303dfbc2d3b6c54098617b031d2e36 Mon Sep 17 00:00:00 2001 From: priyanshuvish Date: Thu, 19 Mar 2026 13:44:16 +0530 Subject: [PATCH] need to fix layout --- package-lock.json | 58 + package.json | 1 + src/App.tsx | 3846 +---------------- src/App_new.tsx | 2343 ---------- src/components/BreadcrumbNav.tsx | 79 + src/components/TopNav.tsx | 308 ++ src/components/shared/ChatBot.tsx | 69 + src/components/shared/KPICard.tsx | 84 + src/components/ui/navigation-menu.tsx | 6 +- src/hooks/useCountUp.ts | 23 + src/hooks/useLocalStorage.ts | 24 + src/layouts/HRLayout.tsx | 105 + src/layouts/components/BreadcrumbNav.tsx | 63 + src/layouts/components/HRSidebar.tsx | 69 + src/main.tsx | 15 +- src/pages/CourseViewPage/CourseViewPage.tsx | 503 +++ src/pages/Dashboard/DashboardPage.tsx | 164 + src/pages/DiscussionsPage/DiscussionsPage.tsx | 710 +++ src/pages/Learners/LearnersPage.tsx | 823 ++++ .../ProgrammeViewPage/ProgrammeViewPage.tsx | 519 +++ src/pages/ReportsPage/ReportsPage.tsx | 574 +++ src/routes/index.tsx | 53 + src/types/index.ts | 135 + src/utils/mockData.ts | 235 + 24 files changed, 4617 insertions(+), 6192 deletions(-) create mode 100644 src/components/BreadcrumbNav.tsx create mode 100644 src/components/TopNav.tsx create mode 100644 src/components/shared/ChatBot.tsx create mode 100644 src/components/shared/KPICard.tsx create mode 100644 src/hooks/useCountUp.ts create mode 100644 src/hooks/useLocalStorage.ts create mode 100644 src/layouts/HRLayout.tsx create mode 100644 src/layouts/components/BreadcrumbNav.tsx create mode 100644 src/layouts/components/HRSidebar.tsx create mode 100644 src/pages/CourseViewPage/CourseViewPage.tsx create mode 100644 src/pages/Dashboard/DashboardPage.tsx create mode 100644 src/pages/DiscussionsPage/DiscussionsPage.tsx create mode 100644 src/pages/Learners/LearnersPage.tsx create mode 100644 src/pages/ProgrammeViewPage/ProgrammeViewPage.tsx create mode 100644 src/pages/ReportsPage/ReportsPage.tsx create mode 100644 src/routes/index.tsx create mode 100644 src/types/index.ts create mode 100644 src/utils/mockData.ts diff --git a/package-lock.json b/package-lock.json index 5bcb44c..764e1f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", "react-resizable-panels": "^2.1.7", + "react-router-dom": "^7.13.1", "recharts": "^2.15.2", "sonner": "^2.0.3", "tailwind-merge": "*", @@ -2835,6 +2836,19 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3669,6 +3683,44 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", @@ -3803,6 +3855,12 @@ "loose-envify": "^1.1.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", diff --git a/package.json b/package.json index 909892d..9c91917 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", "react-resizable-panels": "^2.1.7", + "react-router-dom": "^7.13.1", "recharts": "^2.15.2", "sonner": "^2.0.3", "tailwind-merge": "*", diff --git a/src/App.tsx b/src/App.tsx index 7360e3d..f351504 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3843 +1,9 @@ -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'; -import { Textarea } from './components/ui/textarea'; -import { Checkbox } from './components/ui/checkbox'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './components/ui/select'; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from './components/ui/dialog'; -import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './components/ui/sheet'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './components/ui/table'; -import { Progress } from './components/ui/progress'; -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 { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PaginationEllipsis } from './components/ui/pagination'; -import { - Home, - Users, - BarChart3, - MessageSquare, - Settings, - Search, - Filter, - Download, - Plus, - Upload, - Edit, - MoreHorizontal, - TrendingUp, - Calendar, - Clock, - BookOpen, - Award, - Bell, - ChevronDown, - Menu, - X, - ArrowLeft, - ChevronRight, - RefreshCw, - CheckCircle, - AlertCircle, - XCircle, - FileText, - Mail, - Phone, - Eye, - Trash2, - Building2, - CreditCard, - Shield, - ExternalLink, - 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'; +import React from 'react'; +import { RouterProvider } from 'react-router-dom'; +import { router } from './routes'; -// Types -interface KPIData { - title: string; - value: number; - change?: number; - trend?: 'up' | 'down' | 'neutral'; +function App() { + return ; } -interface Employee { - id: string; - name: string; - email: string; - phone: string; - status: 'Active' | 'Inactive' | 'Pending'; - programme?: string; - course?: string; - progress?: number; - lastActivity?: string; -} - -interface Announcement { - id: string; - title: string; - content: string; - type: 'announcement' | 'reminder'; - timestamp: string; - pinned?: boolean; -} - -interface Deadline { - id: string; - title: string; - type: 'webinar' | 'profiler'; - dueDate: string; - 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; - phone: string; - organisation: string; - programme: string; - testimonialText: string; - 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(() => { - try { - const item = window.localStorage.getItem(key); - return item ? JSON.parse(item) : initialValue; - } catch (error) { - return initialValue; - } - }); - - const setValue = (value: any) => { - try { - setStoredValue(value); - window.localStorage.setItem(key, JSON.stringify(value)); - } catch (error) { - console.error('Error saving to localStorage:', error); - } - }; - - return [storedValue, setValue]; -}; - -const useCountUp = (end: number, duration: number = 1200) => { - const [count, setCount] = useState(0); - - useEffect(() => { - let start = 0; - const increment = end / (duration / 16); - const timer = setInterval(() => { - start += increment; - if (start >= end) { - setCount(end); - clearInterval(timer); - } else { - setCount(Math.floor(start)); - } - }, 16); - - return () => clearInterval(timer); - }, [end, duration]); - - return count; -}; - -// Mock data -const mockKPIData: KPIData[] = [ - { title: 'Total Learners', value: 1247, change: 12, trend: 'up' }, - { title: 'Active Courses', value: 89, change: 5, trend: 'up' }, - { title: 'Completed Profilers', value: 342, change: -8, trend: 'down' }, - { title: 'Average Progress', value: 73, change: 7, trend: 'up' } -]; - -const mockEmployees: Employee[] = [ - { id: '1', name: 'Sarah Chen', email: 'sarah.chen@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Leadership Development', course: 'Strategic Thinking', progress: 85, lastActivity: '2 hours ago' }, - { id: '2', name: 'Michael Rodriguez', email: 'michael.r@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Technical Skills', course: 'Data Analysis', progress: 62, lastActivity: '1 day ago' }, - { id: '3', name: 'Emma Thompson', email: 'emma.thompson@company.com', phone: '+61 4XX XXX XXX', status: 'Pending', programme: 'Communication', course: 'Public Speaking', progress: 0, lastActivity: 'Never' }, - { id: '4', name: 'David Kim', email: 'david.kim@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Project Management', course: 'Agile Methodology', progress: 94, lastActivity: '3 hours ago' }, - { id: '5', name: 'Lisa Wang', email: 'lisa.wang@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Leadership Development', course: 'Team Management', progress: 78, lastActivity: '5 hours ago' }, - { id: '6', name: 'James Wilson', email: 'james.wilson@company.com', phone: '+61 4XX XXX XXX', status: 'Inactive', programme: 'Technical Skills', course: 'Programming Basics', progress: 34, lastActivity: '2 weeks ago' }, - { id: '7', name: 'Maria Garcia', email: 'maria.garcia@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Sales Training', course: 'Customer Relations', progress: 56, lastActivity: '1 day ago' }, - { id: '8', name: 'Robert Lee', email: 'robert.lee@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Leadership Development', course: 'Decision Making', progress: 89, lastActivity: '4 hours ago' }, - { id: '9', name: 'Jennifer Davis', email: 'jennifer.davis@company.com', phone: '+61 4XX XXX XXX', status: 'Pending', programme: 'Communication', course: 'Written Communication', progress: 0, lastActivity: 'Never' }, - { id: '10', name: 'Thomas Brown', email: 'thomas.brown@company.com', phone: '+61 4XX XXX XXX', status: 'Active', programme: 'Project Management', course: 'Risk Management', progress: 71, lastActivity: '6 hours ago' } -]; - -const mockAnnouncements: Announcement[] = [ - { id: '1', title: 'New Learning Module Available', content: 'Advanced Analytics course is now live in the system.', type: 'announcement', timestamp: '2 hours ago', pinned: true }, - { id: '2', title: 'Reminder: Quarterly Reviews Due', content: 'Please complete all quarterly progress reviews by Friday.', type: 'reminder', timestamp: '5 hours ago' }, - { id: '3', title: 'System Maintenance Scheduled', content: 'Learning platform will be offline Saturday 2-4 AM for updates.', type: 'announcement', timestamp: '1 day ago' } -]; - -const mockDeadlines: Deadline[] = [ - { id: '1', title: 'Leadership Webinar Series', type: 'webinar', dueDate: 'Today', dueTime: '2:00 PM' }, - { id: '2', title: 'Communication Skills Assessment', type: 'profiler', dueDate: 'Tomorrow', dueTime: '11:59 PM' }, - { id: '3', title: 'Project Management Workshop', type: 'webinar', dueDate: 'Dec 30', dueTime: '10:00 AM' }, - { id: '4', title: 'Technical Skills Profiler', type: 'profiler', dueDate: 'Jan 2', dueTime: '5:00 PM' }, - { 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); - const [prefersReducedMotion] = useLocalStorage('prefersReducedMotion', false); - - return ( - { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onClick?.(); - } - }} - > - - {data.title} - - -
- - {prefersReducedMotion ? data.value : countedValue} - {data.title.includes('Progress') && '%'} - - {data.change && ( - - {data.trend === 'up' ? '+' : ''}{data.change}{data.title.includes('Progress') ? '%' : ''} - - )} -
-
-
- ); -}; - -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} - -
-
- -
- - {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; - viewMode?: 'table' | 'cards'; -}> = ({ employees, onEdit, showProgress = true, maxHeight = "400px", viewMode = 'table' }) => { - if (viewMode === 'cards') { - return ( -
- {employees.map((employee) => ( - - ))} -
- ); - } - - return ( -
- - - - Employee - Email - Phone - Status - {showProgress && ( - <> - Programme/Course - Progress - Last Activity - - )} - Actions - - - - {employees.map((employee) => ( - onEdit?.(employee)} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onEdit?.(employee); - } - }} - > - {employee.name} - {employee.email} - {employee.phone} - - - {employee.status} - - - Employee status is {employee.status} - - - {showProgress && ( - <> - -
-
{employee.programme}
-
{employee.course}
-
-
- - {employee.progress !== undefined && ( -
- - {employee.progress}% - - Progress: {employee.progress} percent complete - -
- )} -
- {employee.lastActivity} - - )} - - - -
- ))} -
-
-
- ); -}; - -const HRSidebar: React.FC<{ - activeScreen: string; - onNavigate: (screen: string) => void; - className?: string; -}> = ({ activeScreen, onNavigate, className = '' }) => { - const [prefersReducedMotion] = useLocalStorage('prefersReducedMotion', false); - - const menuItems = [ - { id: 'home', label: 'Dashboard', icon: Home, path: '/hr/home' }, - { id: 'learners', label: 'Learners', icon: Users, path: '/hr/learners' }, - { id: 'reports', label: 'Reports', icon: BarChart3, path: '/hr/reports' }, - { id: 'discussions', label: 'Discussion Forums', icon: MessageSquare, path: '/hr/discussions' } - ]; - - return ( -
-
-
-
- TS -
- Tech Solutions Pvt Ltd -
-
- - -
- ); -}; - -const TopNav: React.FC<{ - onMenuToggle?: () => void; - showMenuButton?: boolean; - onNotificationToggle?: () => void; - notificationCount?: number; -}> = ({ onMenuToggle, showMenuButton = false, onNotificationToggle, notificationCount = 0 }) => { - return ( -
-
- {showMenuButton && ( - - )} -
- Kautilya Leadership Centre -
-
- -
- - - {/* HR Profile Dropdown */} - - - - - - {/* User Profile Section */} -
-
- P -
-
-

Priya

-

Tech Solutions Pvt Ltd

-
-
- - {/* Switch Mode Section */} -
-

• Switch Mode

-
- - -
-
- - {/* Switch Accounts Section */} -
-

• Switch Accounts

-
- - -
-
- - - - {/* Profile & Settings */} - - -
-

Profile & Settings

-

Manage your account

-
-
- - {/* Sign out */} - - -
-

Sign out

-

End your session

-
-
-
-
-
-
- ); -}; - -const BreadcrumbNav: React.FC<{ currentScreen: string; currentProgrammeId?: string | null; currentCourseId?: string | null }> = ({ currentScreen, currentProgrammeId, currentCourseId }) => { - const getBreadcrumbText = (screen: string) => { - switch (screen) { - case 'home': return 'Dashboard'; - case 'learners': return 'Learners'; - 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'; - } - }; - - return ( - - - - HR Portal - - - - ); -}; - -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} - - -
-
- ))} -
- -
- -
-
- ); -}; - -const ChatBot: React.FC<{ currentScreen?: string }> = ({ currentScreen }) => { - const [isOpen, setIsOpen] = useState(false); - - const getChipsForScreen = (screen?: string) => { - if (screen === 'profile') { - return [ - "How do I submit a testimonial?", - "When will my testimonial be reviewed?", - "Can I edit my testimonial?", - "What makes a good testimonial?" - ]; - } - return [ - "How do I upload a roster?", - "How to assign courses?", - "View progress reports", - "Export learner data" - ]; - }; - - const chips = getChipsForScreen(currentScreen); - - return ( -
- {isOpen && ( -
-
-

HR Assistant

- -
-
- {chips.map((chip, index) => ( - - ))} -
-
- )} - -
- ); -}; - -// Screen Components -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), 800); - return () => clearTimeout(timer); - }, []); - - if (loading) { - return ( -
-
- {[...Array(4)].map((_, i) => ( - - - - - - - - - ))} -
- - - - - - - - -
- ); - } - - return ( -
- {/* Welcome Section */} -
-
-

Welcome Priya 👋

-

Manage programmes, track progress, and stay connected with your learning community

-
-
- - {/* Quick Actions Section - Unchanged */} - - - Quick Actions - Common HR tasks and shortcuts - - -
- {[ - { title: 'Add Learners', icon: Plus, action: () => onNavigate('learners', { action: 'add' }) }, - { 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 ( - - ); - })} -
-
-
- - {/* 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}`)} - /> -
-
- ); -}; - -const LearnersScreen: React.FC<{ filters?: any }> = ({ filters }) => { - const [employees, setEmployees] = useState(mockEmployees); - const [searchTerm, setSearchTerm] = useState(''); - const [statusFilter, setStatusFilter] = useState('all'); - const [selectedEmployees, setSelectedEmployees] = useState([]); - const [showAddDrawer, setShowAddDrawer] = useState(false); - const [showImportModal, setShowImportModal] = useState(false); - const [showAssignModal, setShowAssignModal] = useState(false); - const [showEditDrawer, setShowEditDrawer] = useState(false); - 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) => { - // Simulating search with 300ms delay - const timer = setTimeout(() => { - setSearchTerm(term); - }, 300); - return () => clearTimeout(timer); - }, - [] - ); - - const filteredEmployees = employees.filter(emp => { - const matchesSearch = emp.name.toLowerCase().includes(searchTerm.toLowerCase()) || - emp.email.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesStatus = statusFilter === 'all' || emp.status === statusFilter; - 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]); - } else { - setSelectedEmployees(prev => prev.filter(id => id !== employeeId)); - } - }; - - const handleBulkSelect = (selectAll: boolean) => { - if (selectAll) { - 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]); - - const handleAddEmployee = () => { - if (newEmployee.name && newEmployee.email) { - const newEmp: Employee = { - id: Date.now().toString(), - name: newEmployee.name, - email: newEmployee.email, - phone: newEmployee.phone, - status: 'Pending' - }; - setEmployees(prev => [...prev, newEmp]); - setNewEmployee({ name: '', email: '', phone: '' }); - setShowAddDrawer(false); - // Show success toast (simulated) - console.log('Employee added successfully'); - } - }; - - const handleEditEmployee = (employee: Employee) => { - setEditingEmployee(employee); - setShowEditDrawer(true); - }; - - return ( -
- {/* Toolbar */} - - -
-
-
- - debouncedSearch(e.target.value)} - aria-label="Search learners by name or email" - /> -
- -
-
- - -
-
-
-
- - {/* Bulk Action Bar */} - {bulkActionVisible && ( - - -
- - {selectedEmployees.length} learner{selectedEmployees.length !== 1 ? 's' : ''} selected {paginatedEmployees.length > 0 && selectedEmployees.length === paginatedEmployees.length ? '(current page)' : ''} - -
- - - -
-
-
-
- )} - - {/* Learners Table */} - - -
-
- Learners ({filteredEmployees.length}) - - Manage learner accounts and assignments • - Showing {startIndex + 1}-{Math.min(endIndex, filteredEmployees.length)} of {filteredEmployees.length} learners - -
-
- - -
-
-
- -
- - - - Select - Name - Email - Phone - Status - Actions - - - - {paginatedEmployees.map((employee) => ( - - - handleEmployeeSelect(employee.id, !!checked)} - className="min-tap-44" - aria-label={`Select ${employee.name}`} - /> - - {employee.name} - {employee.email} - {employee.phone} - - - {employee.status} - - - -
- - -
-
-
- ))} -
-
-
- - {/* 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 */} - - - - Add New Learner - - Add a new learner to the system. Email cannot be changed after saving. - - -
-
- - setNewEmployee(prev => ({ ...prev, name: e.target.value }))} - placeholder="Enter full name" - required - aria-required="true" - /> -
-
- - setNewEmployee(prev => ({ ...prev, email: e.target.value }))} - placeholder="email@company.com" - required - aria-required="true" - /> -
-
- - setNewEmployee(prev => ({ ...prev, phone: e.target.value }))} - placeholder="+61 4XX XXX XXX" - /> -
-
- - -
-
-
-
- - {/* Import Modal */} - - - - Import Learners - - Upload a CSV file to import multiple learners. Maximum file size: 5MB. - - -
-
-

Step 1: Download Template

-

- Download our CSV template with the required fields: Name, Email, Phone. -

- -
-
-

Step 2: Upload File

-
- -

- Drag and drop your CSV file here, or click to browse -

- -
-
-
- - -
-
-
-
- - {/* Assign Modal */} - - - - Assign to Programme/Course - - Assign {selectedEmployees.length} selected learner{selectedEmployees.length !== 1 ? 's' : ''} to a programme or course. - - -
-
- - -
-
- - -
-
- - -
-
-
-
- - {/* Edit/Assign Drawer */} - - - - - {editingEmployee?.name} - - - Edit learner details and manage course assignments. - - - {editingEmployee && ( - - - Details - Enrolments - - -
- - -
-
- - -
-
- - -
-
- -
-
-

Current Enrolments

- -
- {editingEmployee.programme && ( - - -
-
-

{editingEmployee.programme}

-

{editingEmployee.course}

- {editingEmployee.progress !== undefined && ( - - )} -
- -
-
-
- )} -
-
-
- - -
-
- )} -
-
-
- ); -}; - - - -const TestimonialsTabContent: React.FC = () => { - const [loading, setLoading] = useState(true); - const [formData, setFormData] = useState({ - name: 'Alex Sharma', - email: 'alex.sharma@company.com', - phone: '', - organisation: 'Acme Corp', - programme: '', - testimonialText: '', - consentToPublish: false - }); - const [formErrors, setFormErrors] = useState>({}); - const [isSubmitting, setIsSubmitting] = useState(false); - const [submitSuccess, setSubmitSuccess] = useState(false); - const [submitError, setSubmitError] = useState(''); - const [charCount, setCharCount] = useState(0); - const [prefersReducedMotion] = useLocalStorage('prefersReducedMotion', false); - - // Simulate profile pre-fill loading - useEffect(() => { - const timer = setTimeout(() => setLoading(false), 300); - return () => clearTimeout(timer); - }, []); - - const programmes = [ - 'Leadership Development', - 'Technical Skills', - 'Communication', - 'Project Management', - 'Sales Training' - ]; - - const validateForm = () => { - const errors: Record = {}; - - if (!formData.testimonialText.trim()) { - errors.testimonialText = 'Testimonial text is required'; - } else if (formData.testimonialText.length < 1) { - errors.testimonialText = 'Testimonial must be at least 1 character'; - } else if (formData.testimonialText.length > 2000) { - errors.testimonialText = 'Testimonial must be 2000 characters or less'; - } - - if (!formData.consentToPublish) { - errors.consentToPublish = 'You must consent to publish your testimonial'; - } - - return errors; - }; - - const isFormValid = () => { - const errors = validateForm(); - return Object.keys(errors).length === 0 && formData.testimonialText.trim().length > 0; - }; - - const handleInputChange = (field: keyof TestimonialFormData, value: string | boolean) => { - setFormData(prev => ({ ...prev, [field]: value })); - - if (field === 'testimonialText' && typeof value === 'string') { - setCharCount(value.length); - } - - // Clear field error when user starts typing - if (formErrors[field]) { - setFormErrors(prev => { - const newErrors = { ...prev }; - delete newErrors[field]; - return newErrors; - }); - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - const errors = validateForm(); - if (Object.keys(errors).length > 0) { - setFormErrors(errors); - return; - } - - setIsSubmitting(true); - setSubmitError(''); - - try { - // Simulate API call - await new Promise(resolve => setTimeout(resolve, 1500)); - - // Simulate occasional error for testing - if (Math.random() > 0.9) { - throw new Error('Submission failed. Please try again.'); - } - - setSubmitSuccess(true); - setFormData(prev => ({ ...prev, testimonialText: '', programme: '', consentToPublish: false })); - setCharCount(0); - } catch (error) { - setSubmitError(error instanceof Error ? error.message : 'An error occurred. Please try again.'); - } finally { - setIsSubmitting(false); - } - }; - - const resetForm = () => { - setSubmitSuccess(false); - setSubmitError(''); - setFormErrors({}); - }; - - if (loading) { - return ( -
- - - - - - -
- - - -
-
-
-
- ); - } - - return ( -
- {/* Success State */} - {submitSuccess && ( - - - - Thanks — your testimonial is pending review by KLC. - - - - )} - - {/* Error State */} - {submitError && ( - - - - {submitError} - - - )} - - {/* Testimonial Form */} - - - Submit Testimonial - - Share your experience with KLC programmes to help others discover the value of our learning solutions. - - - -
-
- {/* Your Name */} -
- - -

- Pre-filled from your profile -

-
- - {/* Work Email */} -
- - -

- Pre-filled from your profile -

-
- - {/* Phone */} -
- - handleInputChange('phone', e.target.value)} - placeholder="+61 4XX XXX XXX" - className="min-tap-44" - /> -
- - {/* Organisation */} -
- - -

- Pre-filled from your profile -

-
-
- - {/* Programme */} -
- - -
- - {/* Testimonial Text */} -
- -