![]() 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/ |
import { useEffect, useState } from 'react';
import type { NextPage } from 'next';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import CaseAssignmentDashboard from '@/components/CaseAssignmentDashboard';
import { canAccessAdmin } from '@/lib/auth-utils';
import toast from 'react-hot-toast';
interface User {
id: string;
name: string;
email: string;
role: string;
title?: string;
lawFirm?: {
name: string;
shortName?: string;
};
}
interface LegalCase {
id: string;
title: string;
caseNumber: string;
status: string;
priority: string;
jurisdiction: string;
court?: string;
firmName?: string;
leadLawyer?: {
id: string;
name: string;
email: string;
};
_count?: {
caseAssignments: number;
registrations: number;
};
}
interface CaseAssignment {
id: string;
role: 'lead_lawyer' | 'primary_lawyer' | 'assistant_lawyer' | 'secretary';
assignedAt: string;
user: User;
legalCase: LegalCase;
}
const CaseAssignmentsPage: NextPage = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [loading, setLoading] = useState(true);
const [users, setUsers] = useState<User[]>([]);
const [cases, setCases] = useState<LegalCase[]>([]);
const [selectedView, setSelectedView] = useState<'dashboard' | 'assign' | 'manage'>('dashboard');
useEffect(() => {
if (status === 'loading') return;
if (!session?.user) {
router.push('/auth/login');
return;
}
if (!canAccessAdmin(session)) {
router.push('/');
return;
}
fetchData();
}, [session, status, router]);
const fetchData = async () => {
try {
const [usersRes, casesRes] = await Promise.all([
fetch('/api/admin/users'),
fetch('/api/admin/cases')
]);
if (usersRes.ok) {
const usersData = await usersRes.json();
setUsers(usersData.users || []);
}
if (casesRes.ok) {
const casesData = await casesRes.json();
setCases(casesData.cases || []);
}
} catch (error) {
console.error('Error fetching data:', error);
toast.error('Failed to load data');
} finally {
setLoading(false);
}
};
const assignUserToCase = async (caseId: string, userId: string, role: string) => {
try {
const response = await fetch('/api/admin/case-assignments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ caseId, userId, role })
});
if (response.ok) {
toast.success('Team member assigned successfully!');
fetchData();
} else {
const error = await response.json();
toast.error(error.error || 'Failed to assign team member');
}
} catch (error) {
console.error('Error assigning user:', error);
toast.error('Failed to assign team member');
}
};
if (status === 'loading' || loading) {
return (
<LayoutWithSidebar>
<div className="flex justify-center items-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary mx-auto"></div>
<p className="mt-4 text-gray-600">Loading case assignments...</p>
</div>
</div>
</LayoutWithSidebar>
);
}
if (!session?.user) return null;
return (
<LayoutWithSidebar>
<div className="space-y-6">
{/* Header with Navigation */}
<div className="bg-white shadow rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
🏛️ Case Assignment Management
</h1>
<p className="text-gray-600 mt-1">
Manage team assignments and workflow for legal cases
</p>
</div>
<div className="px-6 py-3">
<nav className="flex space-x-1">
{[
{ id: 'dashboard', label: '📊 My Dashboard' },
{ id: 'assign', label: '➕ Assign Teams' }
].map((tab) => (
<button
key={tab.id}
onClick={() => setSelectedView(tab.id as any)}
className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
selectedView === tab.id
? 'bg-primary text-white'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
}`}
>
{tab.label}
</button>
))}
</nav>
</div>
</div>
{/* Content based on selected view */}
{selectedView === 'dashboard' && (
<CaseAssignmentDashboard currentUser={session.user} />
)}
{selectedView === 'assign' && (
<CaseAssignmentForm
users={users}
cases={cases}
onAssign={assignUserToCase}
onRefresh={fetchData}
/>
)}
</div>
</LayoutWithSidebar>
);
};
// Case Assignment Form Component
const CaseAssignmentForm = ({
users,
cases,
onAssign,
onRefresh
}: {
users: User[];
cases: LegalCase[];
onAssign: (caseId: string, userId: string, role: string) => void;
onRefresh: () => void;
}) => {
const [selectedCase, setSelectedCase] = useState('');
const [selectedUser, setSelectedUser] = useState('');
const [selectedRole, setSelectedRole] = useState('');
const [existingTeam, setExistingTeam] = useState<CaseAssignment[]>([]);
const [loadingTeam, setLoadingTeam] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (selectedCase && selectedUser && selectedRole) {
onAssign(selectedCase, selectedUser, selectedRole);
setSelectedUser('');
setSelectedRole('');
// Keep case selected to continue adding team members
fetchCaseTeam(selectedCase);
}
};
const fetchCaseTeam = async (caseId: string) => {
if (!caseId) return;
setLoadingTeam(true);
try {
const response = await fetch(`/api/admin/case-assignments?caseId=${caseId}`);
if (response.ok) {
const team = await response.json();
setExistingTeam(team);
}
} catch (error) {
console.error('Error fetching team:', error);
} finally {
setLoadingTeam(false);
}
};
const handleCaseChange = (caseId: string) => {
setSelectedCase(caseId);
setSelectedUser('');
setSelectedRole('');
if (caseId) {
fetchCaseTeam(caseId);
} else {
setExistingTeam([]);
}
};
const getRoleInfo = (role: string) => {
switch (role) {
case 'lead_lawyer':
return {
label: 'Lead Attorney',
description: 'Overall case responsibility, strategic decisions, court appearances',
icon: '⚖️',
requirements: 'LAWYER, ADMIN, or SUPERADMIN',
color: 'bg-blue-100 text-blue-800 border-blue-200'
};
case 'primary_lawyer':
return {
label: 'Primary Attorney',
description: 'Core legal work, client interaction, document preparation',
icon: '🏛️',
requirements: 'LAWYER, ADMIN, or SUPERADMIN',
color: 'bg-indigo-100 text-indigo-800 border-indigo-200'
};
case 'assistant_lawyer':
return {
label: 'Assistant Attorney',
description: 'Legal research, document drafting, case support',
icon: '📚',
requirements: 'LAWYER, CLERK, ADMIN, or SUPERADMIN',
color: 'bg-purple-100 text-purple-800 border-purple-200'
};
case 'secretary':
return {
label: 'Legal Secretary',
description: 'Administrative support, scheduling, document management',
icon: '📋',
requirements: 'SECRETARY, ASSISTANT, ADMIN, or SUPERADMIN',
color: 'bg-green-100 text-green-800 border-green-200'
};
default:
return { label: role, description: '', icon: '👤', requirements: '', color: 'bg-gray-100 text-gray-800 border-gray-200' };
}
};
const canAssignRole = (userRole: string, assignmentRole: string) => {
const roleValidation = {
'lead_lawyer': ['LAWYER', 'ADMIN', 'SUPERADMIN'],
'primary_lawyer': ['LAWYER', 'ADMIN', 'SUPERADMIN'],
'assistant_lawyer': ['LAWYER', 'CLERK', 'ADMIN', 'SUPERADMIN'],
'secretary': ['SECRETARY', 'ASSISTANT', 'ADMIN', 'SUPERADMIN']
};
return roleValidation[assignmentRole as keyof typeof roleValidation]?.includes(userRole) || false;
};
const eligibleUsers = users.filter(user =>
selectedRole ? canAssignRole(user.role, selectedRole) : true
);
const selectedCaseData = cases.find(c => c.id === selectedCase);
const selectedUserData = users.find(u => u.id === selectedUser);
return (
<div className="space-y-6">
{/* Case Team Assignment Form */}
<div className="bg-white shadow rounded-lg p-6">
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
👥 Build Legal Team
</h2>
<p className="text-sm text-gray-600 mt-1">
Select a case and assign qualified team members to specific roles
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Case Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
🏛️ Legal Case
</label>
<select
value={selectedCase}
onChange={(e) => handleCaseChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
required
>
<option value="">Select a legal case...</option>
{cases.map((legalCase) => (
<option key={legalCase.id} value={legalCase.id}>
{legalCase.title} ({legalCase.caseNumber})
</option>
))}
</select>
{cases.length === 0 && (
<p className="text-xs text-gray-500 mt-1">No legal cases available</p>
)}
</div>
{/* Role Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
🎯 Team Role
</label>
<select
value={selectedRole}
onChange={(e) => {
setSelectedRole(e.target.value);
setSelectedUser(''); // Reset user selection when role changes
}}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
required
disabled={!selectedCase}
>
<option value="">
{!selectedCase ? 'Select case first...' : 'Choose team role...'}
</option>
{['lead_lawyer', 'primary_lawyer', 'assistant_lawyer', 'secretary'].map((role) => {
const roleInfo = getRoleInfo(role);
return (
<option key={role} value={role}>
{roleInfo.icon} {roleInfo.label}
</option>
);
})}
</select>
</div>
{/* User Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
👤 Team Member
</label>
<select
value={selectedUser}
onChange={(e) => setSelectedUser(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
required
disabled={!selectedRole}
>
<option value="">
{!selectedRole ? 'Select role first...' : 'Choose team member...'}
</option>
{eligibleUsers.map((user) => (
<option key={user.id} value={user.id}>
{user.name} ({user.role}) {user.lawFirm?.shortName ? `- ${user.lawFirm.shortName}` : ''}
</option>
))}
</select>
{selectedRole && eligibleUsers.length === 0 && (
<p className="text-xs text-red-500 mt-1">
No users eligible for this role
</p>
)}
</div>
</div>
{/* Role Description */}
{selectedRole && (
<div className={`border rounded-lg p-4 ${getRoleInfo(selectedRole).color}`}>
<div className="flex items-start gap-3">
<div className="text-2xl">{getRoleInfo(selectedRole).icon}</div>
<div className="flex-1">
<h4 className="font-semibold mb-1">
{getRoleInfo(selectedRole).label}
</h4>
<p className="text-sm mb-2">
{getRoleInfo(selectedRole).description}
</p>
<p className="text-xs">
<strong>Requirements:</strong> {getRoleInfo(selectedRole).requirements}
</p>
</div>
</div>
</div>
)}
{/* Assignment Summary */}
{selectedCase && selectedUser && selectedRole && (
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 className="font-semibold text-green-900 mb-2">✅ Ready to Assign</h4>
<div className="text-sm text-green-800 space-y-1">
<p><strong>Case:</strong> {selectedCaseData?.title} ({selectedCaseData?.caseNumber})</p>
<p><strong>Team Member:</strong> {selectedUserData?.name} ({selectedUserData?.role})</p>
<p><strong>Role:</strong> {getRoleInfo(selectedRole).label}</p>
</div>
</div>
)}
<div className="flex justify-end pt-4">
<button
type="submit"
disabled={!selectedCase || !selectedUser || !selectedRole}
className="bg-primary text-white px-6 py-3 rounded-md hover:bg-primary-dark transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
👥 Add to Team
</button>
</div>
</form>
</div>
{/* Current Team Display */}
{selectedCase && (
<div className="bg-white shadow rounded-lg p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
👥 Current Team: {selectedCaseData?.title}
</h3>
<button
onClick={() => fetchCaseTeam(selectedCase)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
🔄 Refresh
</button>
</div>
{loadingTeam ? (
<div className="text-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary mx-auto"></div>
</div>
) : existingTeam.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<div className="text-4xl mb-3">👥</div>
<p>No team members assigned yet</p>
<p className="text-sm mt-1">Use the form above to build your legal team</p>
</div>
) : (
<div className="space-y-3">
{existingTeam.map((assignment) => {
const roleInfo = getRoleInfo(assignment.role);
return (
<div key={assignment.id} className="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:bg-gray-50">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gradient-to-r from-primary to-primary-dark rounded-full flex items-center justify-center text-white font-semibold">
{assignment.user.name.charAt(0).toUpperCase()}
</div>
<div>
<div className="font-medium text-gray-900">{assignment.user.name}</div>
<div className="text-sm text-gray-600">{assignment.user.email}</div>
{assignment.user.lawFirm && (
<div className="text-xs text-blue-600">{assignment.user.lawFirm.name}</div>
)}
</div>
</div>
<div className="text-right">
<span className={`inline-flex items-center gap-1 px-3 py-1 text-sm rounded-full border ${roleInfo.color}`}>
<span>{roleInfo.icon}</span>
<span>{roleInfo.label}</span>
</span>
<div className="text-xs text-gray-500 mt-1">
Since {new Date(assignment.assignedAt).toLocaleDateString()}
</div>
</div>
</div>
);
})}
</div>
)}
</div>
)}
</div>
);
};
export default CaseAssignmentsPage;