![]() 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/admin/ |
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { toast } from 'react-hot-toast';
interface User {
id: string;
name: string;
email: string;
role: string;
title?: string;
isProfilePublic: boolean;
createdAt: string;
lastActive?: string;
}
interface SystemStats {
totalUsers: number;
totalCases: number;
totalDocuments: number;
activeUsers: number;
activeCases: number;
totalApplications: number;
lawFirms: number;
roleDistribution: { [key: string]: number };
}
const SuperAdminDashboard: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [users, setUsers] = useState<User[]>([]);
const [stats, setStats] = useState<SystemStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (status === 'loading') return;
if (!session || session.user.role !== 'SUPERADMIN') {
router.push('/admin');
return;
}
fetchData();
}, [session, status, router]);
const fetchData = async () => {
try {
const [usersResponse, registrationsResponse, casesResponse] = await Promise.all([
fetch('/api/admin/users'),
fetch('/api/admin/registrations').catch(() => null),
fetch('/api/admin/cases').catch(() => null)
]);
let totalUsers = 0;
let totalCases = 0;
let activeCases = 0;
let totalApplications = 0;
let totalDocuments = 0;
let activeUsers = 0;
let lawFirms = 1; // ADW firm
let roleDistribution: { [key: string]: number } = {};
let usersData: User[] = [];
// Process users data
if (usersResponse && usersResponse.ok) {
const usersResult = await usersResponse.json();
const users = usersResult.users || usersResult; // Handle both formats
usersData = users.map((user: any) => ({
id: user.id,
name: user.name || 'No Name',
email: user.email,
role: user.role,
title: user.title,
isProfilePublic: user.isProfilePublic || false,
createdAt: user.createdAt,
lastActive: user.lastActive
}));
totalUsers = users.length;
// Calculate active users (active in last 24 hours)
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
activeUsers = users.filter((user: any) =>
user.lastActive && new Date(user.lastActive) > oneDayAgo
).length;
// Calculate role distribution
roleDistribution = users.reduce((acc: any, user: any) => {
acc[user.role] = (acc[user.role] || 0) + 1;
return acc;
}, {});
}
// Process cases data (new multi-case system)
if (casesResponse && casesResponse.ok) {
const casesData = await casesResponse.json();
const cases = casesData.cases || [];
totalCases = cases.length;
activeCases = cases.filter((c: any) => c.status === 'active').length;
totalApplications = cases.reduce((sum: number, c: any) => sum + (c._count?.registrations || 0), 0);
}
// Process registrations/applications data (fallback)
if (registrationsResponse && registrationsResponse.ok && totalApplications === 0) {
const registrations = await registrationsResponse.json();
totalApplications = Array.isArray(registrations) ? registrations.length : 0;
}
// Estimate documents
totalDocuments = totalApplications * 2; // Rough estimate
setStats({
totalUsers,
totalCases,
totalDocuments,
activeUsers,
activeCases,
totalApplications,
lawFirms,
roleDistribution
});
setUsers(usersData);
} catch (error) {
console.error('Error fetching data:', error);
toast.error('Error loading dashboard data');
// Fallback to minimal data
setStats({
totalUsers: 0,
totalCases: 0,
totalDocuments: 0,
activeUsers: 0,
activeCases: 0,
totalApplications: 0,
lawFirms: 0,
roleDistribution: {}
});
setUsers([]);
} finally {
setLoading(false);
}
};
const getRoleBadgeColor = (role: string) => {
switch (role) {
case 'SUPERADMIN': return 'bg-red-500 text-white';
case 'ADMIN': return 'bg-purple-500 text-white';
case 'LAWYER': return 'bg-blue-500 text-white';
case 'SECRETARY': return 'bg-green-500 text-white';
case 'ASSISTANT': return 'bg-yellow-500 text-black';
case 'CLERK': return 'bg-orange-500 text-white';
case 'USER': return 'bg-gray-500 text-white';
default: return 'bg-gray-500 text-white';
}
};
if (status === 'loading' || loading) {
return (
<LayoutWithSidebar>
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-600"></div>
</div>
</LayoutWithSidebar>
);
}
if (!session || session.user.role !== 'SUPERADMIN') {
return null;
}
return (
<LayoutWithSidebar>
<div className="max-w-7xl mx-auto p-6">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white flex items-center">
π’ Legal Representative & CEO Dashboard
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-2">
LibertΓ© MΓͺme En Prison - Multi-case platform oversight
</p>
<div className="mt-3 p-3 bg-blue-50 border-l-4 border-blue-500 rounded">
<p className="text-sm text-blue-700 font-medium">
ποΈ Class Action Case: Bordeaux Detention Center (2024QCCS4539)
</p>
<p className="text-xs text-blue-600 mt-1">
Lead Attorney: Justin Wee | ADW Law Firm | Quebec Superior Court
</p>
</div>
</div>
{/* Enhanced System Stats */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg p-6 text-white">
<h3 className="text-sm font-medium opacity-90">Total Users</h3>
<p className="text-3xl font-bold">{stats.totalUsers}</p>
<p className="text-sm opacity-75 mt-1">{stats.activeUsers} active today</p>
</div>
<div className="bg-gradient-to-r from-indigo-500 to-indigo-600 rounded-lg p-6 text-white">
<h3 className="text-sm font-medium opacity-90">Legal Cases</h3>
<p className="text-3xl font-bold">{stats.totalCases}</p>
<p className="text-sm opacity-75 mt-1">{stats.activeCases} active cases</p>
</div>
<div className="bg-gradient-to-r from-green-500 to-green-600 rounded-lg p-6 text-white">
<h3 className="text-sm font-medium opacity-90">Applications</h3>
<p className="text-3xl font-bold">{stats.totalApplications}</p>
<p className="text-sm opacity-75 mt-1">Client registrations</p>
</div>
<div className="bg-gradient-to-r from-purple-500 to-purple-600 rounded-lg p-6 text-white">
<h3 className="text-sm font-medium opacity-90">Law Firms</h3>
<p className="text-3xl font-bold">{stats.lawFirms}</p>
<p className="text-sm opacity-75 mt-1">Partner firms</p>
</div>
</div>
)}
{/* Role Distribution */}
{stats && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
Role Distribution
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4">
{Object.entries(stats.roleDistribution).map(([role, count]) => (
<div key={role} className="text-center">
<div className={`inline-block px-3 py-1 rounded-full text-sm font-medium ${getRoleBadgeColor(role)}`}>
{role}
</div>
<p className="text-2xl font-bold text-gray-900 dark:text-white mt-2">{count}</p>
</div>
))}
</div>
</div>
)}
{/* Multi-Case System Status */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
βοΈ Multi-Case Legal Platform Status
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4">
<div className="flex items-center">
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm">β</span>
</div>
<div className="ml-3">
<p className="font-medium text-green-800 dark:text-green-200">Case Management Active</p>
<p className="text-sm text-green-600 dark:text-green-400">{stats?.totalCases || 0} cases configured</p>
</div>
</div>
</div>
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div className="flex items-center">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm">π₯</span>
</div>
<div className="ml-3">
<p className="font-medium text-blue-800 dark:text-blue-200">ADW Law Firm</p>
<p className="text-sm text-blue-600 dark:text-blue-400">4 lawyers + Justin (ADMIN)</p>
</div>
</div>
</div>
<div className="bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 rounded-lg p-4">
<div className="flex items-center">
<div className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm">π</span>
</div>
<div className="ml-3">
<p className="font-medium text-purple-800 dark:text-purple-200">Auto-Assignment</p>
<p className="text-sm text-purple-600 dark:text-purple-400">AI-powered case matching</p>
</div>
</div>
</div>
</div>
</div>
{/* Team Members */}
{users && users.length > 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
π₯ Team Members ({users.length})
</h2>
<button
onClick={() => router.push('/admin/users')}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Manage All Users
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{users.map((user) => (
<div key={user.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<h3 className="font-medium text-gray-900 dark:text-white">
{user.name || 'No Name'}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">{user.email}</p>
{user.title && (
<p className="text-sm text-blue-600 dark:text-blue-400 mt-1">{user.title}</p>
)}
</div>
<div className={`px-2 py-1 rounded-full text-xs font-medium ${getRoleBadgeColor(user.role)}`}>
{user.role}
</div>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500 dark:text-gray-400">
Joined: {new Date(user.createdAt).toLocaleDateString()}
</span>
<div className="flex items-center space-x-2">
{user.isProfilePublic && (
<span className="text-green-600 dark:text-green-400" title="Public Profile">
π
</span>
)}
{user.lastActive && new Date(user.lastActive) > new Date(Date.now() - 24 * 60 * 60 * 1000) ? (
<span className="text-green-600 dark:text-green-400" title="Active today">
π’
</span>
) : (
<span className="text-gray-400" title="Inactive">
β«
</span>
)}
</div>
</div>
</div>
))}
</div>
{/* Role Summary */}
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-3">Team Overview</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center">
<div className="text-2xl font-bold text-red-600 dark:text-red-400">
{users.filter(u => u.role === 'SUPERADMIN').length}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Super Admins</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{users.filter(u => u.role === 'LAWYER').length}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Lawyers</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600 dark:text-purple-400">
{users.filter(u => u.role === 'ADMIN').length}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Admins</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-600 dark:text-gray-400">
{users.filter(u => u.role === 'USER').length}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Clients</div>
</div>
</div>
</div>
</div>
)}
{/* Enhanced Quick Actions */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
π Multi-Case Legal Platform Management
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Core Features */}
<button
onClick={() => router.push('/admin/case-management')}
className="p-4 border-2 border-indigo-300 dark:border-indigo-600 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg hover:bg-indigo-100 dark:hover:bg-indigo-900/30 transition-colors text-left"
>
<div className="text-2xl mb-2">βοΈ</div>
<h3 className="font-medium text-indigo-900 dark:text-indigo-200">Case Management</h3>
<p className="text-sm text-indigo-700 dark:text-indigo-400">Create & manage legal cases</p>
<span className="inline-block mt-1 px-2 py-1 bg-indigo-200 dark:bg-indigo-800 text-indigo-800 dark:text-indigo-200 text-xs rounded-full">CORE</span>
</button>
<button
onClick={() => router.push('/admin/case-assignments')}
className="p-4 border-2 border-blue-300 dark:border-blue-600 bg-blue-50 dark:bg-blue-900/20 rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors text-left"
>
<div className="text-2xl mb-2">π€</div>
<h3 className="font-medium text-blue-900 dark:text-blue-200">AI Case Assignments</h3>
<p className="text-sm text-blue-700 dark:text-blue-400">Smart team matching & automation</p>
<span className="inline-block mt-1 px-2 py-1 bg-blue-200 dark:bg-blue-800 text-blue-800 dark:text-blue-200 text-xs rounded-full">AI</span>
</button>
<button
onClick={() => router.push('/admin/analytics-dashboard')}
className="p-4 border-2 border-purple-300 dark:border-purple-600 bg-purple-50 dark:bg-purple-900/20 rounded-lg hover:bg-purple-100 dark:hover:bg-purple-900/30 transition-colors text-left"
>
<div className="text-2xl mb-2">π</div>
<h3 className="font-medium text-purple-900 dark:text-purple-200">Analytics Dashboard</h3>
<p className="text-sm text-purple-700 dark:text-purple-400">Real-time insights & predictions</p>
<span className="inline-block mt-1 px-2 py-1 bg-purple-200 dark:bg-purple-800 text-purple-800 dark:text-purple-200 text-xs rounded-full">ANALYTICS</span>
</button>
{/* Standard Features */}
<button
onClick={() => router.push('/admin/registrations')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">π</div>
<h3 className="font-medium text-gray-900 dark:text-white">Applications</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Review client applications</p>
</button>
<button
onClick={() => router.push('/admin/newsletter')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">π§</div>
<h3 className="font-medium text-gray-900 dark:text-white">Newsletter System</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Manage campaigns & subscribers</p>
</button>
<button
onClick={() => router.push('/profiles')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">π₯</div>
<h3 className="font-medium text-gray-900 dark:text-white">Team Directory</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">View public profiles</p>
</button>
{/* Coming Soon Features */}
<button
onClick={() => toast.success('Advanced features coming soon!')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">β°</div>
<h3 className="font-medium text-gray-900 dark:text-white">Time Tracking</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Coming soon</p>
</button>
<button
onClick={() => toast.success('Advanced features coming soon!')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">π°</div>
<h3 className="font-medium text-gray-900 dark:text-white">Billing System</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Coming soon</p>
</button>
<button
onClick={() => toast.success('Advanced features coming soon!')}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-left"
>
<div className="text-2xl mb-2">π
</div>
<h3 className="font-medium text-gray-900 dark:text-white">Calendar Integration</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Coming soon</p>
</button>
</div>
</div>
</div>
</LayoutWithSidebar>
);
};
export default SuperAdminDashboard;