![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/backups/lavocat.quebec/backup-20250730-021618/src/pages/judge/ |
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import { useRequireRole } from '../../lib/auth-utils';
import { USER_ROLES } from '../../lib/auth-utils';
interface Course {
id: string;
title: string;
category: 'ethics' | 'procedural' | 'substantive' | 'technology' | 'leadership';
level: 'beginner' | 'intermediate' | 'advanced';
duration: string;
credits: number;
instructor: string;
status: 'available' | 'in_progress' | 'completed' | 'upcoming';
startDate?: string;
endDate?: string;
description: string;
topics: string[];
materials: string[];
isRequired: boolean;
}
interface TrainingEvent {
id: string;
title: string;
type: 'conference' | 'workshop' | 'seminar' | 'webinar';
date: string;
duration: string;
location: string;
organizer: string;
status: 'upcoming' | 'ongoing' | 'completed';
description: string;
speakers: string[];
maxParticipants: number;
registeredParticipants: number;
}
const JudgeEducationPage: React.FC = () => {
const router = useRouter();
const { session, status, isAuthorized } = useRequireRole([USER_ROLES.JUDGE, USER_ROLES.ADMIN, USER_ROLES.SUPERADMIN, USER_ROLES.SUPERADMIN]);
const [selectedCategory, setSelectedCategory] = useState<string>('all');
const [selectedLevel, setSelectedLevel] = useState<string>('all');
const [searchTerm, setSearchTerm] = useState('');
const [activeTab, setActiveTab] = useState('courses');
// Mock data for courses
const [courses] = useState<Course[]>([
{
id: '1',
title: 'Judicial Ethics and Professional Conduct',
category: 'ethics',
level: 'intermediate',
duration: '8 hours',
credits: 8,
instructor: 'Prof. Sarah Johnson',
status: 'completed',
startDate: '2025-05-15',
endDate: '2025-05-22',
description: 'Comprehensive course covering judicial ethics, conflicts of interest, and professional conduct standards for judges.',
topics: [
'Judicial independence and impartiality',
'Conflict of interest identification and management',
'Ex parte communications',
'Social media and judicial conduct',
'Recusal standards and procedures'
],
materials: [
'Judicial Ethics Handbook',
'Case Studies in Judicial Ethics',
'Video Lectures',
'Interactive Scenarios'
],
isRequired: true
},
{
id: '2',
title: 'Digital Evidence and Technology in the Courtroom',
category: 'technology',
level: 'advanced',
duration: '12 hours',
credits: 12,
instructor: 'Dr. Michael Chen',
status: 'in_progress',
startDate: '2025-06-20',
endDate: '2025-07-05',
description: 'Advanced training on handling digital evidence, electronic discovery, and technology integration in court proceedings.',
topics: [
'Digital forensics fundamentals',
'Electronic discovery procedures',
'Technology-assisted review',
'Digital evidence authentication',
'Cybersecurity in judicial systems'
],
materials: [
'Digital Evidence Guide',
'Forensic Software Training',
'Expert Witness Testimony',
'Practical Exercises'
],
isRequired: false
},
{
id: '3',
title: 'Administrative Law Update',
category: 'substantive',
level: 'intermediate',
duration: '6 hours',
credits: 6,
instructor: 'Prof. Emily Rodriguez',
status: 'available',
startDate: '2025-07-10',
endDate: '2025-07-17',
description: 'Update on recent developments in administrative law, including Supreme Court decisions and procedural changes.',
topics: [
'Recent Supreme Court decisions',
'Standard of review developments',
'Procedural fairness updates',
'Administrative tribunal procedures',
'Judicial review trends'
],
materials: [
'Administrative Law Update 2025',
'Case Law Analysis',
'Legislative Changes Summary',
'Practice Guidelines'
],
isRequired: false
},
{
id: '4',
title: 'Court Management and Leadership',
category: 'leadership',
level: 'advanced',
duration: '10 hours',
credits: 10,
instructor: 'Hon. David Thompson',
status: 'upcoming',
startDate: '2025-08-01',
endDate: '2025-08-15',
description: 'Leadership training for senior judges covering court administration, team management, and organizational leadership.',
topics: [
'Court administration principles',
'Team leadership and motivation',
'Change management in judicial settings',
'Strategic planning for courts',
'Crisis management and communication'
],
materials: [
'Leadership in Judicial Settings',
'Management Case Studies',
'Strategic Planning Tools',
'Communication Skills Workshop'
],
isRequired: false
}
]);
// Mock data for training events
const [events] = useState<TrainingEvent[]>([
{
id: '1',
title: 'Annual Judicial Conference 2025',
type: 'conference',
date: '2025-09-15',
duration: '3 days',
location: 'Quebec City Convention Center',
organizer: 'Quebec Judicial Council',
status: 'upcoming',
description: 'Annual gathering of judges from across Quebec featuring keynote speakers, panel discussions, and networking opportunities.',
speakers: ['Chief Justice Marie Dubois', 'Prof. Sarah Johnson', 'Dr. Michael Chen'],
maxParticipants: 200,
registeredParticipants: 150
},
{
id: '2',
title: 'Technology in the Courtroom Workshop',
type: 'workshop',
date: '2025-07-25',
duration: '1 day',
location: 'Montreal Courthouse',
organizer: 'Judicial Technology Committee',
status: 'upcoming',
description: 'Hands-on workshop demonstrating new courtroom technologies and digital tools for judges.',
speakers: ['Dr. Michael Chen', 'IT Specialist Lisa Wang'],
maxParticipants: 30,
registeredParticipants: 25
},
{
id: '3',
title: 'Ethics in the Digital Age Webinar',
type: 'webinar',
date: '2025-06-30',
duration: '2 hours',
location: 'Online',
organizer: 'Judicial Ethics Board',
status: 'completed',
description: 'Webinar addressing ethical challenges judges face in the digital age, including social media and online communications.',
speakers: ['Prof. Sarah Johnson', 'Ethics Officer Robert Smith'],
maxParticipants: 100,
registeredParticipants: 85
}
]);
const getCategoryColor = (category: string) => {
switch (category) {
case 'ethics': return 'bg-red-100 text-red-800';
case 'procedural': return 'bg-blue-100 text-blue-800';
case 'substantive': return 'bg-green-100 text-green-800';
case 'technology': return 'bg-purple-100 text-purple-800';
case 'leadership': return 'bg-orange-100 text-orange-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const getLevelColor = (level: string) => {
switch (level) {
case 'beginner': return 'bg-green-100 text-green-800';
case 'intermediate': return 'bg-yellow-100 text-yellow-800';
case 'advanced': return 'bg-red-100 text-red-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'available':
case 'upcoming':
return 'bg-green-100 text-green-800';
case 'in_progress':
case 'ongoing':
return 'bg-blue-100 text-blue-800';
case 'completed':
return 'bg-gray-100 text-gray-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const getTypeColor = (type: string) => {
switch (type) {
case 'conference': return 'bg-purple-100 text-purple-800';
case 'workshop': return 'bg-blue-100 text-blue-800';
case 'seminar': return 'bg-green-100 text-green-800';
case 'webinar': return 'bg-orange-100 text-orange-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const filteredCourses = courses.filter(course => {
const matchesCategory = selectedCategory === 'all' || course.category === selectedCategory;
const matchesLevel = selectedLevel === 'all' || course.level === selectedLevel;
const matchesSearch = course.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.instructor.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesLevel && matchesSearch;
});
if (status === 'loading') {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading judicial education...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-6">
<div>
<h1 className="text-3xl font-bold text-gray-900">Judicial Education</h1>
<p className="mt-1 text-sm text-gray-500">
Access training resources, continuing education, and professional development
</p>
</div>
<div className="flex space-x-3">
<button
onClick={() => router.push('/judge/dashboard')}
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
← Back to Dashboard
</button>
<button className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
+ Request Course
</button>
</div>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Stats Overview */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">Total Courses</dt>
<dd className="text-lg font-medium text-gray-900">{courses.length}</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-green-500 rounded-md flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">Completed</dt>
<dd className="text-lg font-medium text-gray-900">
{courses.filter(c => c.status === 'completed').length}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-orange-500 rounded-md flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">In Progress</dt>
<dd className="text-lg font-medium text-gray-900">
{courses.filter(c => c.status === 'in_progress').length}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-purple-500 rounded-md flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">Total Credits</dt>
<dd className="text-lg font-medium text-gray-900">
{courses.filter(c => c.status === 'completed').reduce((sum, course) => sum + course.credits, 0)}
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
{/* Tabs */}
<div className="bg-white shadow rounded-lg mb-6">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8 px-6">
<button
onClick={() => setActiveTab('courses')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'courses'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Courses
</button>
<button
onClick={() => setActiveTab('events')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'events'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Training Events
</button>
</nav>
</div>
<div className="p-6">
{/* Filters */}
<div className="mb-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-4 sm:space-y-0">
<div className="flex-1 max-w-lg">
<input
type="search"
placeholder="Search courses and events..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
/>
</div>
<div className="flex space-x-4">
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
>
<option value="all">All Categories</option>
<option value="ethics">Ethics</option>
<option value="procedural">Procedural</option>
<option value="substantive">Substantive</option>
<option value="technology">Technology</option>
<option value="leadership">Leadership</option>
</select>
<select
value={selectedLevel}
onChange={(e) => setSelectedLevel(e.target.value)}
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
>
<option value="all">All Levels</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
</div>
</div>
{/* Courses Tab */}
{activeTab === 'courses' && (
<div className="space-y-6">
{filteredCourses.map((course) => (
<div key={course.id} className="border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className="text-lg font-medium text-gray-900">{course.title}</h3>
{course.isRequired && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
Required
</span>
)}
</div>
<p className="text-sm text-gray-600 mb-4">{course.description}</p>
<div className="flex items-center space-x-4 mb-4">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getCategoryColor(course.category)}`}>
{course.category}
</span>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getLevelColor(course.level)}`}>
{course.level}
</span>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(course.status)}`}>
{course.status.replace('_', ' ')}
</span>
<span className="text-sm text-gray-500">
{course.duration} • {course.credits} credits
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-medium text-gray-900 mb-2">Topics Covered</h4>
<ul className="text-sm text-gray-600 space-y-1">
{course.topics.slice(0, 3).map((topic, index) => (
<li key={index} className="flex items-start">
<span className="text-blue-500 mr-2">•</span>
{topic}
</li>
))}
{course.topics.length > 3 && (
<li className="text-sm text-gray-500">
+{course.topics.length - 3} more topics
</li>
)}
</ul>
</div>
<div>
<h4 className="text-sm font-medium text-gray-900 mb-2">Materials</h4>
<ul className="text-sm text-gray-600 space-y-1">
{course.materials.slice(0, 3).map((material, index) => (
<li key={index} className="flex items-start">
<span className="text-green-500 mr-2">•</span>
{material}
</li>
))}
{course.materials.length > 3 && (
<li className="text-sm text-gray-500">
+{course.materials.length - 3} more materials
</li>
)}
</ul>
</div>
</div>
</div>
<div className="ml-6 flex flex-col items-end">
<p className="text-sm text-gray-500 mb-2">Instructor</p>
<p className="text-sm font-medium text-gray-900 mb-4">{course.instructor}</p>
<div className="flex space-x-2">
<button className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
{course.status === 'available' ? 'Enroll' : course.status === 'in_progress' ? 'Continue' : 'View'}
</button>
<button className="inline-flex items-center px-3 py-1 border border-gray-300 text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Details
</button>
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Events Tab */}
{activeTab === 'events' && (
<div className="space-y-6">
{events.map((event) => (
<div key={event.id} className="border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className="text-lg font-medium text-gray-900">{event.title}</h3>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getTypeColor(event.type)}`}>
{event.type}
</span>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(event.status)}`}>
{event.status}
</span>
</div>
<p className="text-sm text-gray-600 mb-4">{event.description}</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-medium text-gray-900 mb-2">Event Details</h4>
<div className="text-sm text-gray-600 space-y-1">
<p><span className="font-medium">Date:</span> {new Date(event.date).toLocaleDateString()}</p>
<p><span className="font-medium">Duration:</span> {event.duration}</p>
<p><span className="font-medium">Location:</span> {event.location}</p>
<p><span className="font-medium">Organizer:</span> {event.organizer}</p>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-gray-900 mb-2">Speakers</h4>
<ul className="text-sm text-gray-600 space-y-1">
{event.speakers.map((speaker, index) => (
<li key={index} className="flex items-start">
<span className="text-purple-500 mr-2">•</span>
{speaker}
</li>
))}
</ul>
<div className="mt-3">
<p className="text-sm text-gray-500">
{event.registeredParticipants}/{event.maxParticipants} participants registered
</p>
</div>
</div>
</div>
</div>
<div className="ml-6 flex flex-col items-end">
<div className="flex space-x-2">
<button className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
{event.status === 'upcoming' ? 'Register' : event.status === 'ongoing' ? 'Join' : 'View'}
</button>
<button className="inline-flex items-center px-3 py-1 border border-gray-300 text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Details
</button>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default JudgeEducationPage;