![]() 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 CaseNote {
id: string;
caseNumber: string;
caseTitle: string;
title: string;
content: string;
type: 'observation' | 'ruling' | 'evidence' | 'procedural' | 'personal';
priority: 'low' | 'medium' | 'high' | 'urgent';
createdAt: string;
updatedAt: string;
tags: string[];
isPrivate: boolean;
}
const JudgeNotesPage: React.FC = () => {
const router = useRouter();
const { session, status, isAuthorized } = useRequireRole([USER_ROLES.JUDGE, USER_ROLES.ADMIN, USER_ROLES.SUPERADMIN, USER_ROLES.SUPERADMIN]);
const [selectedType, setSelectedType] = useState<string>('all');
const [selectedPriority, setSelectedPriority] = useState<string>('all');
const [searchTerm, setSearchTerm] = useState('');
const [showPrivate, setShowPrivate] = useState(true);
const [isCreating, setIsCreating] = useState(false);
// Mock data for case notes
const [notes] = useState<CaseNote[]>([
{
id: '1',
caseNumber: 'CR-2025-001',
caseTitle: 'State v. Johnson',
title: 'Witness Credibility Assessment',
content: 'Defense witness Sarah Martinez appeared nervous during cross-examination. Her testimony about the defendant\'s whereabouts on the night of the incident was inconsistent with her initial statement to police. Need to review surveillance footage to corroborate her timeline.',
type: 'observation',
priority: 'high',
createdAt: '2025-06-30 14:30',
updatedAt: '2025-06-30 14:30',
tags: ['witness', 'credibility', 'cross-examination'],
isPrivate: false
},
{
id: '2',
caseNumber: 'CV-2025-045',
caseTitle: 'Smith v. Corporation Ltd',
title: 'Evidence Admissibility Ruling',
content: 'Plaintiff\'s motion to admit email correspondence from 2023 is GRANTED. The emails are relevant to establishing the timeline of events and the defendant\'s knowledge of the disputed contract terms. Defense objections regarding hearsay are overruled as the emails fall under the business records exception.',
type: 'ruling',
priority: 'medium',
createdAt: '2025-06-29 16:45',
updatedAt: '2025-06-29 16:45',
tags: ['evidence', 'admissibility', 'email', 'hearsay'],
isPrivate: false
},
{
id: '3',
caseNumber: 'FA-2025-012',
caseTitle: 'In re: Estate of Williams',
title: 'Personal Reflection on Settlement',
content: 'The parties seem close to reaching a settlement. The executor has shown flexibility on the distribution percentages, and the beneficiaries appear willing to compromise. This could be resolved through mediation rather than trial.',
type: 'personal',
priority: 'low',
createdAt: '2025-06-28 11:20',
updatedAt: '2025-06-28 11:20',
tags: ['settlement', 'mediation', 'compromise'],
isPrivate: true
},
{
id: '4',
caseNumber: 'AD-2025-078',
caseTitle: 'Department of Transportation v. Chen',
title: 'Procedural Timeline Notes',
content: 'Administrative hearing scheduled for July 15th. Need to ensure all documentary evidence is properly authenticated. Respondent has requested additional time to prepare expert witness testimony. Consider extending discovery deadline by 30 days.',
type: 'procedural',
priority: 'medium',
createdAt: '2025-06-27 09:15',
updatedAt: '2025-06-27 09:15',
tags: ['hearing', 'evidence', 'discovery', 'expert'],
isPrivate: false
}
]);
const getTypeColor = (type: string) => {
switch (type) {
case 'observation': return 'bg-blue-100 text-blue-800';
case 'ruling': return 'bg-green-100 text-green-800';
case 'evidence': return 'bg-purple-100 text-purple-800';
case 'procedural': return 'bg-orange-100 text-orange-800';
case 'personal': return 'bg-gray-100 text-gray-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'low': return 'bg-green-100 text-green-800';
case 'medium': return 'bg-yellow-100 text-yellow-800';
case 'high': return 'bg-orange-100 text-orange-800';
case 'urgent': return 'bg-red-100 text-red-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const filteredNotes = notes.filter(note => {
const matchesType = selectedType === 'all' || note.type === selectedType;
const matchesPriority = selectedPriority === 'all' || note.priority === selectedPriority;
const matchesSearch = note.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
note.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
note.caseNumber.toLowerCase().includes(searchTerm.toLowerCase());
const matchesPrivacy = showPrivate || !note.isPrivate;
return matchesType && matchesPriority && matchesSearch && matchesPrivacy;
});
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 case notes...</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">Case Notes</h1>
<p className="mt-1 text-sm text-gray-500">
Manage and organize case-related notes and observations
</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
onClick={() => setIsCreating(true)}
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"
>
+ New Note
</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="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 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 Notes</dt>
<dd className="text-lg font-medium text-gray-900">{notes.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">Public Notes</dt>
<dd className="text-lg font-medium text-gray-900">
{notes.filter(n => !n.isPrivate).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 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">High Priority</dt>
<dd className="text-lg font-medium text-gray-900">
{notes.filter(n => n.priority === 'high' || n.priority === 'urgent').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="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">Unique Tags</dt>
<dd className="text-lg font-medium text-gray-900">
{new Set(notes.flatMap(n => n.tags)).size}
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
{/* Filters */}
<div className="bg-white shadow rounded-lg mb-6">
<div className="px-6 py-4 border-b border-gray-200">
<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">
<label htmlFor="search" className="sr-only">Search notes</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
id="search"
name="search"
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Search notes by title, content, or case number..."
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="flex space-x-4">
<select
value={selectedType}
onChange={(e) => setSelectedType(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 Types</option>
<option value="observation">Observation</option>
<option value="ruling">Ruling</option>
<option value="evidence">Evidence</option>
<option value="procedural">Procedural</option>
<option value="personal">Personal</option>
</select>
<select
value={selectedPriority}
onChange={(e) => setSelectedPriority(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 Priorities</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
<label className="flex items-center">
<input
type="checkbox"
checked={showPrivate}
onChange={(e) => setShowPrivate(e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">Show Private</span>
</label>
</div>
</div>
</div>
</div>
{/* Notes List */}
<div className="space-y-6">
{filteredNotes.map((note) => (
<div key={note.id} className="bg-white shadow rounded-lg overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<h3 className="text-lg font-medium text-gray-900">{note.title}</h3>
{note.isPrivate && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
Private
</span>
)}
</div>
<div className="flex items-center space-x-2">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getTypeColor(note.type)}`}>
{note.type}
</span>
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getPriorityColor(note.priority)}`}>
{note.priority}
</span>
</div>
</div>
<div className="mt-2">
<p className="text-sm text-gray-600">
Case: <span className="font-medium">{note.caseNumber}</span> - {note.caseTitle}
</p>
<p className="text-xs text-gray-500 mt-1">
Created: {new Date(note.createdAt).toLocaleDateString()} at {new Date(note.createdAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</p>
</div>
</div>
<div className="px-6 py-4">
<p className="text-sm text-gray-700 whitespace-pre-wrap">{note.content}</p>
{note.tags.length > 0 && (
<div className="mt-4 flex flex-wrap gap-2">
{note.tags.map((tag, index) => (
<span
key={index}
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
#{tag}
</span>
))}
</div>
)}
</div>
<div className="px-6 py-3 bg-gray-50 border-t border-gray-200">
<div className="flex justify-between items-center">
<p className="text-xs text-gray-500">
Last updated: {new Date(note.updatedAt).toLocaleDateString()}
</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">
Edit
</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">
View Case
</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">
Share
</button>
</div>
</div>
</div>
</div>
))}
</div>
{filteredNotes.length === 0 && (
<div className="text-center py-12">
<svg className="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 className="mt-2 text-sm font-medium text-gray-900">No notes found</h3>
<p className="mt-1 text-sm text-gray-500">
Try adjusting your search or filter criteria, or create a new note.
</p>
<div className="mt-6">
<button
onClick={() => setIsCreating(true)}
className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm 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"
>
+ Create Note
</button>
</div>
</div>
)}
</div>
</div>
);
};
export default JudgeNotesPage;