![]() 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/domains/lavocat.ca/private_html/src/pages/admin/cases/ |
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import {
ArrowLeft,
Save,
Plus,
AlertCircle,
Users,
Calendar,
Building,
Scale,
FileText,
CheckCircle
} from 'lucide-react';
interface LawFirm {
id: string;
name: string;
shortName: string;
address: string;
city: string;
province: string;
}
interface User {
id: string;
name: string;
email: string;
role: string;
lawFirmId?: string;
title?: string;
specialization?: string;
}
interface CaseFormData {
title: string;
description: string;
caseNumber: string;
caseType: string;
jurisdiction: string;
court: string;
leadLawyerId: string;
firmName: string;
priority: string;
budget: string;
status: string;
applicationDeadline: string;
isAcceptingApplications: boolean;
requiresApproval: boolean;
eligibilityCriteria: {
minimumAge?: number;
maximumAge?: number;
location?: string[];
incomeLimit?: number;
specificConditions?: string;
};
requiredDocuments: string[];
applicationInstructions: string;
}
const CreateCasePage = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [lawyers, setLawyers] = useState<User[]>([]);
const [lawFirms, setLawFirms] = useState<LawFirm[]>([]);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [formData, setFormData] = useState<CaseFormData>({
title: '',
description: '',
caseNumber: '',
caseType: 'class_action',
jurisdiction: 'Quebec',
court: '',
leadLawyerId: '',
firmName: '',
priority: 'medium',
budget: '',
status: 'pending',
applicationDeadline: '',
isAcceptingApplications: true,
requiresApproval: true,
eligibilityCriteria: {},
requiredDocuments: ['Government ID', 'Proof of Detention/Incarceration'],
applicationInstructions: ''
});
const [newDocument, setNewDocument] = useState('');
useEffect(() => {
if (status === 'loading') return;
if (!session || !['SUPERADMIN', 'ADMIN'].includes(session.user.role)) {
router.push('/admin');
return;
}
fetchData();
}, [session, status, router]);
const fetchData = async () => {
try {
const [lawyersRes, firmsRes] = await Promise.all([
fetch('/api/admin/users?role=LAWYER,ADMIN'),
fetch('/api/admin/law-firms')
]);
let lawyersData: any = null;
let firmsData: any = null;
if (lawyersRes.ok) {
lawyersData = await lawyersRes.json();
setLawyers(lawyersData.users || []);
}
if (firmsRes.ok) {
firmsData = await firmsRes.json();
setLawFirms(firmsData.firms || []);
}
// Auto-select current user's firm and set as lead lawyer if ADMIN
if (session?.user.role === 'ADMIN' && lawyersData) {
const currentUser = lawyersData.users?.find((u: User) => u.id === session.user.id);
if (currentUser?.lawFirmId && firmsData) {
const userFirm = firmsData.firms?.find((f: LawFirm) => f.id === currentUser.lawFirmId);
setFormData(prev => ({
...prev,
leadLawyerId: currentUser.id,
firmName: userFirm?.name || ''
}));
}
}
} catch (error) {
console.error('Error fetching data:', error);
setError('Failed to load lawyers and firms');
} finally {
setLoading(false);
}
};
const handleInputChange = (field: keyof CaseFormData, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleEligibilityChange = (field: string, value: any) => {
setFormData(prev => ({
...prev,
eligibilityCriteria: {
...prev.eligibilityCriteria,
[field]: value
}
}));
};
const addDocument = () => {
if (newDocument.trim() && !formData.requiredDocuments.includes(newDocument.trim())) {
setFormData(prev => ({
...prev,
requiredDocuments: [...prev.requiredDocuments, newDocument.trim()]
}));
setNewDocument('');
}
};
const removeDocument = (index: number) => {
setFormData(prev => ({
...prev,
requiredDocuments: prev.requiredDocuments.filter((_, i) => i !== index)
}));
};
const validateForm = () => {
const required = ['title', 'description', 'caseNumber', 'leadLawyerId', 'court'];
const missing = required.filter(field => !formData[field as keyof CaseFormData]);
if (missing.length > 0) {
setError(`Please fill in all required fields: ${missing.join(', ')}`);
return false;
}
// Check case number format (Quebec court format)
if (formData.caseNumber && !/^\d{4}QC[A-Z]{2,3}\d+$/.test(formData.caseNumber)) {
setError('Case number should follow Quebec court format (e.g., 2024QCCS4539)');
return false;
}
return true;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setSaving(true);
setError('');
try {
const response = await fetch('/api/admin/cases', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...formData,
budget: formData.budget ? parseFloat(formData.budget) : null,
eligibilityCriteria: JSON.stringify(formData.eligibilityCriteria)
}),
});
if (response.ok) {
const result = await response.json();
setSuccess('Case created successfully!');
// Redirect to case management after a short delay
setTimeout(() => {
router.push('/admin/case-management');
}, 2000);
} else {
const errorData = await response.json();
setError(errorData.error || 'Failed to create case');
}
} catch (error) {
console.error('Error creating case:', error);
setError('Network error. Please try again.');
} finally {
setSaving(false);
}
};
const caseTypes = [
{ value: 'class_action', label: 'Class Action' },
{ value: 'individual', label: 'Individual Case' },
{ value: 'collective', label: 'Collective Action' },
{ value: 'human_rights', label: 'Human Rights' },
{ value: 'prison_conditions', label: 'Prison Conditions' },
{ value: 'discrimination', label: 'Discrimination' },
{ value: 'negligence', label: 'Negligence' },
{ value: 'constitutional', label: 'Constitutional Challenge' }
];
const jurisdictions = [
'Quebec',
'Federal',
'Ontario',
'British Columbia',
'Alberta',
'Manitoba',
'Saskatchewan',
'Nova Scotia',
'New Brunswick',
'Prince Edward Island',
'Newfoundland and Labrador',
'Northwest Territories',
'Yukon',
'Nunavut'
];
const priorities = [
{ value: 'low', label: 'Low', color: 'text-gray-600' },
{ value: 'medium', label: 'Medium', color: 'text-blue-600' },
{ value: 'high', label: 'High', color: 'text-orange-600' },
{ value: 'urgent', label: 'Urgent', color: 'text-red-600' }
];
const statuses = [
{ value: 'pending', label: 'Pending Approval' },
{ value: 'active', label: 'Active' },
{ value: 'suspended', label: 'Suspended' },
{ value: 'closed', label: 'Closed' }
];
if (loading) {
return (
<LayoutWithSidebar>
<div className="p-8">
<div className="max-w-4xl mx-auto">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 rounded w-1/3"></div>
<div className="h-96 bg-gray-200 rounded"></div>
</div>
</div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="p-6">
{/* Header */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-4">
<button
onClick={() => router.push('/admin/case-management')}
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
<ArrowLeft className="h-5 w-5" />
</button>
<div>
<h1 className="text-2xl font-bold text-gray-900">Create New Legal Case</h1>
<p className="text-gray-600 mt-1">Add a new case for user applications</p>
</div>
</div>
</div>
{/* Form */}
<div className="max-w-4xl mx-auto">
{error && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center">
<AlertCircle className="h-5 w-5 text-red-500 mr-2" />
<span className="text-red-700">{error}</span>
</div>
</div>
)}
{success && (
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4">
<div className="flex items-center">
<CheckCircle className="h-5 w-5 text-green-500 mr-2" />
<span className="text-green-700">{success}</span>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-8">
{/* Basic Information */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<Scale className="h-5 w-5 mr-2 text-blue-600" />
Basic Case Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
Case Title *
</label>
<input
type="text"
value={formData.title}
onChange={(e) => handleInputChange('title', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., Bordeaux Detention Center Class Action"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Case Number *
</label>
<input
type="text"
value={formData.caseNumber}
onChange={(e) => handleInputChange('caseNumber', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., 2024QCCS4539"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Case Type
</label>
<select
value={formData.caseType}
onChange={(e) => handleInputChange('caseType', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{caseTypes.map(type => (
<option key={type.value} value={type.value}>{type.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Jurisdiction
</label>
<select
value={formData.jurisdiction}
onChange={(e) => handleInputChange('jurisdiction', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{jurisdictions.map(jurisdiction => (
<option key={jurisdiction} value={jurisdiction}>{jurisdiction}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Court *
</label>
<input
type="text"
value={formData.court}
onChange={(e) => handleInputChange('court', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., Quebec Superior Court"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
Description *
</label>
<textarea
value={formData.description}
onChange={(e) => handleInputChange('description', e.target.value)}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Detailed description of the case and what it aims to achieve..."
/>
</div>
</div>
</div>
{/* Case Management */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<Users className="h-5 w-5 mr-2 text-blue-600" />
Case Management
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Lead Lawyer *
</label>
<select
value={formData.leadLawyerId}
onChange={(e) => handleInputChange('leadLawyerId', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">Select a lawyer</option>
{lawyers.map(lawyer => (
<option key={lawyer.id} value={lawyer.id}>
{lawyer.name} {lawyer.title ? `(${lawyer.title})` : ''}
{lawyer.specialization ? ` - ${lawyer.specialization}` : ''}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Law Firm
</label>
<input
type="text"
value={formData.firmName}
onChange={(e) => handleInputChange('firmName', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Law firm name"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Priority
</label>
<select
value={formData.priority}
onChange={(e) => handleInputChange('priority', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{priorities.map(priority => (
<option key={priority.value} value={priority.value}>
{priority.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Status
</label>
<select
value={formData.status}
onChange={(e) => handleInputChange('status', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{statuses.map(status => (
<option key={status.value} value={status.value}>
{status.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Budget (CAD)
</label>
<input
type="number"
value={formData.budget}
onChange={(e) => handleInputChange('budget', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00"
step="0.01"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Application Deadline
</label>
<input
type="date"
value={formData.applicationDeadline}
onChange={(e) => handleInputChange('applicationDeadline', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div className="mt-4 space-y-3">
<label className="flex items-center">
<input
type="checkbox"
checked={formData.isAcceptingApplications}
onChange={(e) => handleInputChange('isAcceptingApplications', e.target.checked)}
className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700">Accepting new applications</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={formData.requiresApproval}
onChange={(e) => handleInputChange('requiresApproval', e.target.checked)}
className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700">Requires approval for applications</span>
</label>
</div>
</div>
{/* Required Documents */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<FileText className="h-5 w-5 mr-2 text-blue-600" />
Required Documents
</h2>
<div className="space-y-3">
{formData.requiredDocuments.map((doc, index) => (
<div key={index} className="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
<span className="text-gray-700">{doc}</span>
<button
type="button"
onClick={() => removeDocument(index)}
className="text-red-500 hover:text-red-700 text-sm"
>
Remove
</button>
</div>
))}
<div className="flex gap-2">
<input
type="text"
value={newDocument}
onChange={(e) => setNewDocument(e.target.value)}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Add a required document..."
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), addDocument())}
/>
<button
type="button"
onClick={addDocument}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus className="h-4 w-4" />
</button>
</div>
</div>
</div>
{/* Application Instructions */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">
Application Instructions
</h2>
<textarea
value={formData.applicationInstructions}
onChange={(e) => handleInputChange('applicationInstructions', e.target.value)}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Special instructions for applicants..."
/>
</div>
{/* Submit */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<div className="flex items-center justify-between">
<button
type="button"
onClick={() => router.push('/admin/case-management')}
className="px-6 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={saving}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Creating...
</>
) : (
<>
<Save className="h-4 w-4 mr-2" />
Create Case
</>
)}
</button>
</div>
</div>
</form>
</div>
</div>
</LayoutWithSidebar>
);
};
export default CreateCasePage;