![]() 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/lawyer/ |
import React, { useEffect, useState } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { toast } from 'react-hot-toast';
import { User, Shield, Search, UserPlus, UserMinus, Edit, Eye, CheckCircle, XCircle } from 'lucide-react';
import { useRequireRole, USER_ROLES } from '../../lib/auth-utils';
interface TeamMember {
id: string;
name: string;
email: string;
role: string;
specialization?: string;
yearsOfExperience?: number;
isVerified?: boolean;
isProfilePublic?: boolean;
profilePicture?: string;
phone?: string;
lastActive?: string;
status?: 'ONLINE' | 'OFFLINE' | 'AWAY';
}
const LawyerTeam: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [team, setTeam] = useState<TeamMember[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
const [filter, setFilter] = useState('all');
const [updating, setUpdating] = useState(false);
const { isAuthorized } = useRequireRole([
USER_ROLES.LAWYER,
USER_ROLES.ADMIN,
USER_ROLES.SUPERADMIN
], '/auth/login');
useEffect(() => {
if (status === 'loading') return;
if (!session || !['LAWYER', 'ADMIN', 'SUPERADMIN'].includes(session.user.role)) {
router.push('/');
return;
}
fetchTeam();
}, [session, status, router]);
const fetchTeam = async () => {
try {
setLoading(true);
const response = await fetch('/api/lawyer/team');
if (response.ok) {
const data = await response.json();
setTeam(data.team || []);
} else {
toast.error('Failed to load team');
}
} catch (error) {
toast.error('Failed to load team');
} finally {
setLoading(false);
}
};
const handleRoleChange = async (userId: string, newRole: string) => {
try {
setUpdating(true);
const response = await fetch(`/api/lawyer/team/${userId}/role`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role: newRole })
});
if (response.ok) {
toast.success('Role updated');
fetchTeam();
} else {
toast.error('Failed to update role');
}
} catch (error) {
toast.error('Failed to update role');
} finally {
setUpdating(false);
}
};
const handleRemove = async (userId: string) => {
try {
setUpdating(true);
const response = await fetch(`/api/lawyer/team/${userId}`, {
method: 'DELETE'
});
if (response.ok) {
toast.success('Team member removed');
fetchTeam();
} else {
toast.error('Failed to remove member');
}
} catch (error) {
toast.error('Failed to remove member');
} finally {
setUpdating(false);
}
};
const filteredTeam = team.filter(member => {
const matchesSearch = member.name?.toLowerCase().includes(search.toLowerCase()) || member.email?.toLowerCase().includes(search.toLowerCase());
const matchesFilter = filter === 'all' || member.role === filter;
return matchesSearch && matchesFilter;
});
if (status === 'loading' || loading) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600"></div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="max-w-7xl mx-auto px-4 py-8">
<div className="mb-8 flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Team Management</h1>
<p className="text-gray-600 mt-1">Manage your legal team, assign roles, and track status</p>
</div>
<button
onClick={() => router.push('/lawyer/team/invite')}
className="inline-flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
<UserPlus className="h-4 w-4 mr-2" /> Invite Member
</button>
</div>
<div className="flex flex-col md:flex-row gap-4 mb-6">
<div className="flex-1">
<div className="relative">
<Search className="h-4 w-4 absolute left-3 top-3 text-gray-400" />
<input
type="text"
placeholder="Search team..."
value={search}
onChange={e => setSearch(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500"
/>
</div>
</div>
<select
value={filter}
onChange={e => setFilter(e.target.value)}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500"
>
<option value="all">All Roles</option>
<option value="LAWYER">Lawyer</option>
<option value="ADMIN">Admin</option>
<option value="SUPERADMIN">Super Admin</option>
<option value="ASSISTANT">Assistant</option>
<option value="PARALEGAL">Paralegal</option>
</select>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
Team Members ({filteredTeam.length})
</h2>
</div>
{filteredTeam.length === 0 ? (
<div className="p-12 text-center">
<div className="max-w-sm mx-auto">
<User className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No team members found</h3>
<p className="text-gray-500">Try adjusting your search or filters</p>
</div>
</div>
) : (
<div className="divide-y divide-gray-200">
{filteredTeam.map(member => (
<div key={member.id} className="p-6 hover:bg-gray-50 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="h-12 w-12 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
{member.profilePicture ? (
<img src={member.profilePicture} alt={member.name} className="h-12 w-12 object-cover" />
) : (
<User className="h-8 w-8 text-gray-400" />
)}
</div>
<div>
<div className="flex items-center gap-2">
<span className="font-semibold text-gray-900">{member.name}</span>
{member.isVerified && <CheckCircle className="h-4 w-4 text-green-500" />}
{member.isProfilePublic && <Shield className="h-4 w-4 text-blue-500" />}
</div>
<div className="text-sm text-gray-600">{member.email}</div>
<div className="text-xs text-gray-500">{member.role} {member.specialization && `• ${member.specialization}`}</div>
{member.phone && <div className="text-xs text-gray-400">{member.phone}</div>}
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => router.push(`/lawyer/team/${member.id}`)}
className="p-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-colors"
title="View Profile"
>
<Eye className="h-4 w-4" />
</button>
<button
onClick={() => router.push(`/lawyer/team/${member.id}/tasks`)}
className="p-2 text-purple-600 hover:text-purple-800 hover:bg-purple-100 rounded-lg transition-colors"
title="Assign/View Tasks"
>
<Edit className="h-4 w-4" />
</button>
<select
value={member.role}
onChange={e => handleRoleChange(member.id, e.target.value)}
disabled={updating}
className="px-2 py-1 border border-gray-300 rounded-lg text-xs"
>
<option value="LAWYER">Lawyer</option>
<option value="ADMIN">Admin</option>
<option value="SUPERADMIN">Super Admin</option>
<option value="ASSISTANT">Assistant</option>
<option value="PARALEGAL">Paralegal</option>
</select>
<button
onClick={() => handleRemove(member.id)}
disabled={updating}
className="p-2 text-red-600 hover:text-red-800 hover:bg-red-100 rounded-lg transition-colors"
title="Remove from Team"
>
<UserMinus className="h-4 w-4" />
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</LayoutWithSidebar>
);
};
export default LawyerTeam;