![]() 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 StaffMember {
id: string;
name: string;
role: string;
department: string;
status: 'active' | 'on_leave' | 'off_duty' | 'terminated';
contact: string;
email: string;
phone: string;
hireDate: string;
lastActive: string;
assignedCases: number;
performance: 'excellent' | 'good' | 'satisfactory' | 'needs_improvement';
schedule: string;
supervisor: string;
}
interface StaffSchedule {
id: string;
staffId: string;
date: string;
shift: 'morning' | 'afternoon' | 'night' | 'full_day';
status: 'scheduled' | 'completed' | 'absent' | 'late';
notes: string;
}
const JudgeStaffPage: React.FC = () => {
const router = useRouter();
const { session, status, isAuthorized } = useRequireRole([USER_ROLES.JUDGE, USER_ROLES.ADMIN, USER_ROLES.SUPERADMIN, USER_ROLES.SUPERADMIN]);
const [selectedDepartment, setSelectedDepartment] = useState<string>('all');
const [selectedStatus, setSelectedStatus] = useState<string>('all');
const [searchTerm, setSearchTerm] = useState('');
const [activeTab, setActiveTab] = useState('overview');
// Mock data for staff members
const [staff] = useState<StaffMember[]>([
{
id: '1',
name: 'Sarah Johnson',
role: 'Court Clerk',
department: 'Administration',
status: 'active',
contact: 'sarah.johnson@court.qc.ca',
email: 'sarah.johnson@court.qc.ca',
phone: '(514) 555-0123',
hireDate: '2020-03-15',
lastActive: '2025-06-30 16:45',
assignedCases: 12,
performance: 'excellent',
schedule: 'Monday-Friday, 8:00-16:00',
supervisor: 'Marie Dubois'
},
{
id: '2',
name: 'Michael Chen',
role: 'Bailiff',
department: 'Security',
status: 'active',
contact: 'michael.chen@court.qc.ca',
email: 'michael.chen@court.qc.ca',
phone: '(514) 555-0124',
hireDate: '2019-08-22',
lastActive: '2025-06-30 17:00',
assignedCases: 8,
performance: 'good',
schedule: 'Monday-Friday, 7:00-15:00',
supervisor: 'Jean-Pierre Tremblay'
},
{
id: '3',
name: 'Emily Rodriguez',
role: 'Court Reporter',
department: 'Records',
status: 'on_leave',
contact: 'emily.rodriguez@court.qc.ca',
email: 'emily.rodriguez@court.qc.ca',
phone: '(514) 555-0125',
hireDate: '2021-01-10',
lastActive: '2025-06-28 15:30',
assignedCases: 5,
performance: 'excellent',
schedule: 'Monday-Friday, 9:00-17:00',
supervisor: 'Marie Dubois'
},
{
id: '4',
name: 'David Thompson',
role: 'Legal Assistant',
department: 'Administration',
status: 'active',
contact: 'david.thompson@court.qc.ca',
email: 'david.thompson@court.qc.ca',
phone: '(514) 555-0126',
hireDate: '2022-05-18',
lastActive: '2025-06-30 14:20',
assignedCases: 15,
performance: 'good',
schedule: 'Monday-Friday, 8:30-16:30',
supervisor: 'Marie Dubois'
},
{
id: '5',
name: 'Lisa Wang',
role: 'IT Specialist',
department: 'Technology',
status: 'active',
contact: 'lisa.wang@court.qc.ca',
email: 'lisa.wang@court.qc.ca',
phone: '(514) 555-0127',
hireDate: '2021-11-03',
lastActive: '2025-06-30 13:45',
assignedCases: 0,
performance: 'excellent',
schedule: 'Monday-Friday, 9:00-17:00',
supervisor: 'Jean-Pierre Tremblay'
}
]);
// Mock data for schedules
const [schedules] = useState<StaffSchedule[]>([
{
id: '1',
staffId: '1',
date: '2025-07-01',
shift: 'full_day',
status: 'scheduled',
notes: 'Regular court session support'
},
{
id: '2',
staffId: '2',
date: '2025-07-01',
shift: 'full_day',
status: 'scheduled',
notes: 'Security for criminal cases'
},
{
id: '3',
staffId: '3',
date: '2025-07-01',
shift: 'full_day',
status: 'absent',
notes: 'On leave until July 15'
}
]);
const getStatusColor = (status: string) => {
switch (status) {
case 'active': return 'bg-green-100 text-green-800';
case 'on_leave': return 'bg-yellow-100 text-yellow-800';
case 'off_duty': return 'bg-gray-100 text-gray-800';
case 'terminated': return 'bg-red-100 text-red-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const getPerformanceColor = (performance: string) => {
switch (performance) {
case 'excellent': return 'bg-green-100 text-green-800';
case 'good': return 'bg-blue-100 text-blue-800';
case 'satisfactory': return 'bg-yellow-100 text-yellow-800';
case 'needs_improvement': return 'bg-red-100 text-red-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const getShiftColor = (shift: string) => {
switch (shift) {
case 'morning': return 'bg-blue-100 text-blue-800';
case 'afternoon': return 'bg-orange-100 text-orange-800';
case 'night': return 'bg-purple-100 text-purple-800';
case 'full_day': return 'bg-green-100 text-green-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const filteredStaff = staff.filter(member => {
const matchesDepartment = selectedDepartment === 'all' || member.department === selectedDepartment;
const matchesStatus = selectedStatus === 'all' || member.status === selectedStatus;
const matchesSearch = member.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
member.role.toLowerCase().includes(searchTerm.toLowerCase()) ||
member.email.toLowerCase().includes(searchTerm.toLowerCase());
return matchesDepartment && matchesStatus && 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 staff management...</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">Court Staff Management</h1>
<p className="mt-1 text-sm text-gray-500">
Manage court personnel, schedules, and staff assignments
</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">
+ Add Staff Member
</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="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 Staff</dt>
<dd className="text-lg font-medium text-gray-900">{staff.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">Active Staff</dt>
<dd className="text-lg font-medium text-gray-900">
{staff.filter(s => s.status === 'active').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="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">Total Cases</dt>
<dd className="text-lg font-medium text-gray-900">
{staff.reduce((sum, member) => sum + member.assignedCases, 0)}
</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">On Leave</dt>
<dd className="text-lg font-medium text-gray-900">
{staff.filter(s => s.status === 'on_leave').length}
</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('overview')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Staff Overview
</button>
<button
onClick={() => setActiveTab('schedules')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'schedules'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Schedules
</button>
<button
onClick={() => setActiveTab('performance')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'performance'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Performance
</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 staff by name, role, or email..."
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={selectedDepartment}
onChange={(e) => setSelectedDepartment(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 Departments</option>
<option value="Administration">Administration</option>
<option value="Security">Security</option>
<option value="Records">Records</option>
<option value="Technology">Technology</option>
</select>
<select
value={selectedStatus}
onChange={(e) => setSelectedStatus(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 Statuses</option>
<option value="active">Active</option>
<option value="on_leave">On Leave</option>
<option value="off_duty">Off Duty</option>
<option value="terminated">Terminated</option>
</select>
</div>
</div>
</div>
{/* Staff Overview Tab */}
{activeTab === 'overview' && (
<div>
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Staff Member</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Department</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Performance</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cases</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredStaff.map((member) => (
<tr key={member.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<span className="text-sm font-medium text-gray-700">
{member.name.split(' ').map(n => n[0]).join('')}
</span>
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{member.name}</div>
<div className="text-sm text-gray-500">{member.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{member.role}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{member.department}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(member.status)}`}>
{member.status.replace('_', ' ')}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getPerformanceColor(member.performance)}`}>
{member.performance.replace('_', ' ')}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{member.assignedCases}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button className="text-blue-600 hover:text-blue-900 mr-3">View</button>
<button className="text-green-600 hover:text-green-900 mr-3">Edit</button>
<button className="text-red-600 hover:text-red-900">Contact</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Schedules Tab */}
{activeTab === 'schedules' && (
<div>
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Staff Member</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Shift</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{schedules.map((schedule) => {
const staffMember = staff.find(s => s.id === schedule.staffId);
return (
<tr key={schedule.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{staffMember?.name || 'Unknown'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{new Date(schedule.date).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getShiftColor(schedule.shift)}`}>
{schedule.shift.replace('_', ' ')}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(schedule.status)}`}>
{schedule.status}
</span>
</td>
<td className="px-6 py-4 text-sm text-gray-900 max-w-xs truncate">
{schedule.notes}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button className="text-blue-600 hover:text-blue-900 mr-3">Edit</button>
<button className="text-green-600 hover:text-green-900">Update</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
{/* Performance Tab */}
{activeTab === 'performance' && (
<div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredStaff.map((member) => (
<div key={member.id} className="bg-white border border-gray-200 rounded-lg p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium text-gray-900">{member.name}</h3>
<p className="text-sm text-gray-600">{member.role}</p>
</div>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getPerformanceColor(member.performance)}`}>
{member.performance.replace('_', ' ')}
</span>
</div>
<div className="space-y-3">
<div>
<p className="text-sm text-gray-500">Assigned Cases</p>
<p className="text-lg font-medium text-gray-900">{member.assignedCases}</p>
</div>
<div>
<p className="text-sm text-gray-500">Schedule</p>
<p className="text-sm text-gray-900">{member.schedule}</p>
</div>
<div>
<p className="text-sm text-gray-500">Supervisor</p>
<p className="text-sm text-gray-900">{member.supervisor}</p>
</div>
</div>
<div className="mt-4 flex space-x-2">
<button className="flex-1 inline-flex justify-center items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Review
</button>
<button className="inline-flex items-center px-3 py-2 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">
Schedule
</button>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default JudgeStaffPage;