![]() 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 { 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 { canAccessAdmin } from '@/lib/auth-utils';
import { format } from 'date-fns';
import { motion, AnimatePresence } from 'framer-motion';
import Link from 'next/link';
import DocumentViewer from '@/components/DocumentViewer';
import toast from 'react-hot-toast';
import dynamic from 'next/dynamic';
import { useTheme } from '../../context/ThemeContext';
import { useWebSocket } from '@/context/StableWebSocketContext';
import RegistrationForm from '@/components/RegistrationForm';
import CaseWidget from '@/components/CaseWidget';
import PaymentWidget from '@/components/payments/PaymentWidget';
import DashboardModal from '@/components/DashboardModal';
import { useDashboardModal } from '@/hooks/useDashboardModal';
const PrivateChat = dynamic(() => import('@/components/Chat/PrivateChat'), { ssr: false });
interface Registration {
id: string;
firstName: string;
lastName: string;
email: string;
phone: string;
detaineeInfo: {
name: string;
facility: string;
inmateId: string;
incarcerationDate: string;
expectedReleaseDate?: string;
} | null;
status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'MISSING_DOCUMENTS' | 'DOCUMENTS_UNDER_REVIEW' | 'ADDITIONAL_INFO_NEEDED' | 'VERIFICATION_IN_PROGRESS' | 'LAWYER_VERIFICATION' | 'FACILITY_VERIFICATION' | 'DOCUMENTS_EXPIRED' | 'DOCUMENTS_INCOMPLETE' | 'INFORMATION_MISMATCH' | 'PENDING_PAYMENT' | 'PAYMENT_RECEIVED' | 'PENDING_LAWYER_APPROVAL' | 'PENDING_FACILITY_APPROVAL' | 'ON_HOLD' | 'ESCALATED' | 'FINAL_REVIEW' | 'COMPLETED' | 'WebAd';
statusHistory: {
status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'MISSING_DOCUMENTS' | 'DOCUMENTS_UNDER_REVIEW' | 'ADDITIONAL_INFO_NEEDED' | 'VERIFICATION_IN_PROGRESS' | 'LAWYER_VERIFICATION' | 'FACILITY_VERIFICATION' | 'DOCUMENTS_EXPIRED' | 'DOCUMENTS_INCOMPLETE' | 'INFORMATION_MISMATCH' | 'PENDING_PAYMENT' | 'PAYMENT_RECEIVED' | 'PENDING_LAWYER_APPROVAL' | 'PENDING_FACILITY_APPROVAL' | 'ON_HOLD' | 'ESCALATED' | 'FINAL_REVIEW' | 'COMPLETED';
date: string;
note?: string;
}[];
createdAt: string;
updatedAt: string;
documents?: {
id: string;
name: string;
url: string;
type: string;
}[];
reasonForJoining?: string;
urgentNeeds?: string;
userId: string;
}
interface Case {
id: string;
title: string;
status: string;
leadLawyerId?: string;
createdAt?: string;
}
// Quebec detention facilities list
const QUEBEC_FACILITIES = {
'ABITIBI-TÉMISCAMINGUE': [
{ id: 'amos', name: "Établissement de détention d'Amos" }
],
'BAS-SAINT-LAURENT': [
{ id: 'rimouski', name: "Établissement de détention de Rimouski" }
],
'CAPITALE-NATIONALE': [
{ id: 'quebec-femme', name: 'Établissement de détention de Québec – secteur féminin' },
{ id: 'quebec-homme', name: 'Établissement de détention de Québec – secteur masculin' }
],
'CÔTE-NORD': [
{ id: 'baie-comeau', name: 'Établissement de détention de Baie-Comeau' },
{ id: 'sept-iles', name: 'Établissement de détention de Sept-Îles' }
],
'ESTRIE': [
{ id: 'sherbrooke', name: 'Établissement de détention de Sherbrooke' }
],
'GASPÉSIE-ÎLES-DE-LA-MADELEINE': [
{ id: 'new-carlisle', name: 'Établissement de détention de New Carlisle' },
{ id: 'perce', name: 'Établissement de détention de Percé' },
{ id: 'havre-aubert', name: 'Établissement de détention de Havre-Aubert' }
],
'OUTAOUAIS': [
{ id: 'hull', name: 'Établissement de détention de Hull' }
],
'LAURENTIDES': [
{ id: 'saint-jerome', name: 'Établissement de détention de Saint-Jérôme' }
],
'LAVAL': [
{ id: 'leclerc', name: 'Établissement Leclerc de Laval' }
],
'MAURICIE ET CENTRE-DU-QUÉBEC': [
{ id: 'trois-rivieres', name: 'Établissement de détention de Trois-Rivières' }
],
'MONTEREGIE': [
{ id: 'sorel-tracy', name: 'Établissement de détention de Sorel-Tracy' }
],
'MONTRÉAL': [
{ id: 'bordeaux', name: 'Établissement de détention de Montréal (Bordeaux)' },
{ id: 'riviere-des-prairies', name: 'Établissement de détention de Rivière-des-Prairies' }
],
'SAGUENAY–LAC-SAINT-JEAN': [
{ id: 'roberval', name: 'Établissement de détention de Roberval' }
]
};
function getFacilityName(facilityId: string) {
for (const region of Object.values(QUEBEC_FACILITIES)) {
const facility = region.find(f => f.id === facilityId);
if (facility) return facility.name;
}
return facilityId;
}
// Status helpers (same as user dashboard)
const getStatusColor = (status: string) => {
switch (status) {
case 'APPROVED':
case 'COMPLETED':
case 'PAYMENT_RECEIVED':
return 'bg-green-100 text-green-800';
case 'REJECTED':
case 'DOCUMENTS_EXPIRED':
case 'INFORMATION_MISMATCH':
case 'ESCALATED':
return 'bg-red-100 text-red-800';
case 'PENDING':
case 'ADDITIONAL_INFO_NEEDED':
case 'PENDING_PAYMENT':
return 'bg-yellow-100 text-yellow-800';
case 'MISSING_DOCUMENTS':
case 'DOCUMENTS_INCOMPLETE':
return 'bg-orange-100 text-orange-800';
case 'DOCUMENTS_UNDER_REVIEW':
case 'PENDING_LAWYER_APPROVAL':
case 'PENDING_FACILITY_APPROVAL':
return 'bg-blue-100 text-blue-800';
case 'VERIFICATION_IN_PROGRESS':
case 'FINAL_REVIEW':
return 'bg-purple-100 text-purple-800';
case 'ON_HOLD':
return 'bg-gray-100 text-gray-800';
case 'WebAd':
return 'bg-pink-100 text-pink-800';
default:
return 'bg-yellow-100 text-yellow-800';
}
};
const getStatusLabel = (status: string) => {
const statusMap: { [key: string]: string } = {
PENDING: 'Pending',
APPROVED: 'Approved',
REJECTED: 'Rejected',
MISSING_DOCUMENTS: 'Missing Documents',
DOCUMENTS_UNDER_REVIEW: 'Documents Under Review',
ADDITIONAL_INFO_NEEDED: 'Additional Info Needed',
VERIFICATION_IN_PROGRESS: 'Verification in Progress',
LAWYER_VERIFICATION: 'Lawyer Verification',
FACILITY_VERIFICATION: 'Facility Verification',
DOCUMENTS_EXPIRED: 'Documents Expired',
DOCUMENTS_INCOMPLETE: 'Documents Incomplete',
INFORMATION_MISMATCH: 'Information Mismatch',
PENDING_PAYMENT: 'Pending Payment',
PAYMENT_RECEIVED: 'Payment Received',
PENDING_LAWYER_APPROVAL: 'Pending Lawyer Approval',
PENDING_FACILITY_APPROVAL: 'Pending Facility Approval',
ON_HOLD: 'On Hold',
ESCALATED: 'Escalated',
FINAL_REVIEW: 'Final Review',
COMPLETED: 'Completed',
WebAd: 'Web Advertisement'
};
return statusMap[status] || status;
};
// Status icon component
const StatusIcon = ({ status }: { status: string }) => {
const iconMap: { [key: string]: JSX.Element } = {
APPROVED: <span className="text-green-500 text-2xl">✔️</span>,
REJECTED: <span className="text-red-500 text-2xl">❌</span>,
PENDING: <span className="text-yellow-500 text-2xl">⏳</span>,
DOCUMENTS_UNDER_REVIEW: <span className="text-blue-500 text-2xl">📄</span>,
MISSING_DOCUMENTS: <span className="text-orange-500 text-2xl">📄</span>,
PENDING_FACILITY_APPROVAL: <span className="text-blue-400 text-2xl">🏢</span>,
WebAd: <span className="text-pink-500 text-2xl">🌐</span>,
};
return iconMap[status] || <span className="text-gray-400 text-2xl">📝</span>;
};
const translations = {
en: {
dashboard: 'Admin Dashboard',
stats: {
total: 'Total',
pending: 'Pending',
approved: 'Approved',
rejected: 'Rejected',
},
actions: {
view: 'View',
edit: 'Edit',
delete: 'Delete',
updateStatus: 'Update Status',
},
statusUpdate: {
title: 'Update Status',
note: 'Add a note (optional)',
submit: 'Update',
cancel: 'Cancel',
},
},
fr: {
dashboard: 'Tableau de bord administrateur',
stats: {
total: 'Total',
pending: 'En attente',
approved: 'Approuvées',
rejected: 'Rejetées',
},
actions: {
view: 'Voir',
edit: 'Modifier',
delete: 'Supprimer',
updateStatus: 'Mettre à jour le statut',
},
statusUpdate: {
title: 'Mettre à jour le statut',
note: 'Ajouter une note (optionnel)',
submit: 'Mettre à jour',
cancel: 'Annuler',
},
},
};
const AdminDashboard: NextPage = () => {
const { data: session, status } = useSession();
const router = useRouter();
const { theme } = useTheme();
const { connected, sendMessage } = useWebSocket();
const [registrations, setRegistrations] = useState<Registration[]>([]);
const [cases, setCases] = useState<Case[]>([]);
const [loading, setLoading] = useState(true);
const [selectedRegistration, setSelectedRegistration] = useState<Registration | null>(null);
const [showRegistrationForm, setShowRegistrationForm] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [activeTab, setActiveTab] = useState<'overview' | 'registrations' | 'cases' | 'users' | 'analytics' | 'settings'>('overview');
const { isModalOpen, currentRoute, modalTitle, openModal, closeModal } = useDashboardModal();
const [stats, setStats] = useState({
total: 0,
pending: 0,
approved: 0,
rejected: 0,
webAd: 0
});
const [error, setError] = useState('');
const [showStatusModal, setShowStatusModal] = useState(false);
const [statusNote, setStatusNote] = useState('');
const [selectedDocument, setSelectedDocument] = useState<{
url: string;
type: string;
name: string;
} | null>(null);
const { locale } = router;
const t = translations[locale as 'en' | 'fr'] || translations.en;
const [showWebAdOnly, setShowWebAdOnly] = useState(false);
const [showPrivateChat, setShowPrivateChat] = useState<{ open: boolean; registrationId: string | null }>({ open: false, registrationId: null });
const [showNewApplication, setShowNewApplication] = useState(false);
const [casesLoading, setCasesLoading] = useState(false);
const [casesError, setCasesError] = useState<string | null>(null);
// Mobile detection
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth < 768;
setIsMobile(mobile);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// Request notification permission on component mount
useEffect(() => {
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission().then(permission => {
console.log('[AdminDashboard] Notification permission:', permission);
if (permission === 'granted') {
toast.success('🔔 Notifications enabled! You\'ll receive real-time updates.', {
duration: 4000,
position: 'top-right',
});
}
});
}
}, []);
// WebSocket message handling for notifications
useEffect(() => {
if (!sendMessage || !connected || !session?.user?.id) return;
const handleMessage = (event: MessageEvent) => {
try {
const message = JSON.parse(event.data);
console.log('[AdminDashboard] 📥 WebSocket message received:', message.type);
switch (message.type) {
case 'PRIVATE_MESSAGE':
console.log('[AdminDashboard] 📥 Full private message data:', message.data);
console.log('[AdminDashboard] 📥 Sender ID:', message.data.sender?.id, 'Session User ID:', session.user.id);
console.log('[AdminDashboard] 📥 Registration ID:', message.data.registrationId);
console.log('[AdminDashboard] 📥 Current chat state:', { showPrivateChat });
// Handle private message notifications when chat is closed
if (message.data.sender.id !== session.user.id) {
const isPrivateChatOpen = showPrivateChat.open && showPrivateChat.registrationId === message.data.registrationId;
console.log('[AdminDashboard] 📥 Is private chat open for this registration?', isPrivateChatOpen);
if (!isPrivateChatOpen) {
console.log('[AdminDashboard] 📢 Showing notification toast for private message');
// Show toast notification for new private message
toast((t) => (
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm font-bold">
{message.data.sender.name?.charAt(0) || 'U'}
</span>
</div>
</div>
<div className="flex-1">
<p className="font-medium text-gray-900">
💬 New message from {message.data.sender.name || 'User'}
</p>
<p className="text-sm text-gray-600 truncate max-w-48">
{message.data.content}
</p>
</div>
<button
onClick={() => {
console.log('[AdminDashboard] 🖱️ Toast Reply button clicked for registration:', message.data.registrationId);
toast.dismiss(t.id);
setShowPrivateChat({ open: true, registrationId: message.data.registrationId });
console.log('[AdminDashboard] ✅ Private chat state updated to open');
}}
className="flex-shrink-0 bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
>
Reply
</button>
</div>
), {
duration: 8000, // Increased duration for testing
position: 'top-right',
style: {
maxWidth: '400px',
},
});
// Show browser notification
if (Notification.permission === 'granted') {
new Notification(`New message from ${message.data.sender.name || 'User'}`, {
body: message.data.content,
icon: '/icons/apple-touch-icon-180x180.png'
});
}
console.log(`[AdminDashboard] ✅ Received private message from ${message.data.sender.name}`);
} else {
console.log('[AdminDashboard] ℹ️ Private chat is already open, not showing notification');
}
} else {
console.log('[AdminDashboard] ℹ️ Message is from current user, not showing notification');
}
break;
case 'CHAT_MESSAGE':
// Handle group chat notifications for admin
if (message.data.user.id !== session.user.id) {
toast((t) => (
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm font-bold">
{message.data.user.name?.charAt(0) || 'U'}
</span>
</div>
</div>
<div className="flex-1">
<p className="font-medium text-gray-900">
💬 New group message from {message.data.user.name || 'User'}
</p>
<p className="text-sm text-gray-600 truncate max-w-48">
{message.data.content}
</p>
</div>
<button
onClick={() => {
toast.dismiss(t.id);
router.push('/group-chat');
}}
className="flex-shrink-0 bg-green-500 text-white px-3 py-1 rounded text-sm hover:bg-green-600"
>
View
</button>
</div>
), {
duration: 5000,
position: 'top-right',
style: {
maxWidth: '400px',
},
});
// Show browser notification
if (Notification.permission === 'granted') {
new Notification(`New group message from ${message.data.user.name || 'User'}`, {
body: message.data.content,
icon: '/icons/apple-touch-icon-180x180.png'
});
}
}
break;
case 'APPLICATION_STATUS_UPDATE':
// Handle application status update notifications
if (message.data.adminId === session.user.id) {
toast.success(`✅ Status updated for ${message.data.applicantName}: ${message.data.status}`, {
duration: 4000,
position: 'top-right',
});
// Show browser notification
if (Notification.permission === 'granted') {
new Notification('Application Status Updated', {
body: `${message.data.applicantName}: ${message.data.status}`,
icon: '/icons/apple-touch-icon-180x180.png'
});
}
}
break;
}
} catch (error) {
console.error('[AdminDashboard] ❌ Error parsing WebSocket message:', error);
}
};
// WebSocket message handling is done through the context
// No need to add/remove event listeners manually
}, [sendMessage, connected, session?.user?.id, showPrivateChat, router]);
useEffect(() => {
if (status === 'loading') return; // Don't run until session is loaded
if (status === 'unauthenticated') {
router.push('/auth/login');
return;
}
if (status === 'authenticated' && session && !canAccessAdmin(session)) {
console.log('User role:', session?.user?.role);
console.log('Redirecting non-admin user to user dashboard');
router.push('/user/dashboard');
return;
}
}, [status, session, router]);
useEffect(() => {
const fetchRegistrations = async () => {
try {
setLoading(true);
const response = await fetch('/api/admin/registrations');
if (!response.ok) {
throw new Error('Failed to fetch registrations');
}
const data = await response.json();
setRegistrations(data);
setStats({
total: data.length,
pending: data.filter((r: Registration) => r.status === 'PENDING').length,
approved: data.filter((r: Registration) => r.status === 'APPROVED').length,
rejected: data.filter((r: Registration) => r.status === 'REJECTED').length,
webAd: data.filter((r: Registration) => r.status === 'WebAd').length
});
} catch (err) {
setError('Failed to load registrations');
console.error(err);
} finally {
setLoading(false);
}
};
if (status === 'authenticated') {
fetchRegistrations();
}
}, [status]);
const fetchCases = async (retry = false) => {
setCasesLoading(true);
setCasesError(null);
try {
console.log('🔍 Fetching cases from /api/admin/cases...');
const res = await fetch('/api/admin/cases');
console.log('📊 Cases API response status:', res.status, res.statusText);
if (!res.ok) {
const errorText = await res.text();
console.error('❌ Cases API error response:', errorText);
// If 403 and not already retried, force session refresh and retry after delay
if (res.status === 403 && !retry) {
console.warn('🔄 403 Forbidden - refreshing session and retrying fetchCases...');
await fetch('/api/auth/session?update');
await new Promise(resolve => setTimeout(resolve, 1200));
return fetchCases(true);
}
throw new Error(`Failed to fetch cases: ${res.status} ${res.statusText} - ${errorText}`);
}
const data = await res.json();
console.log('✅ Cases API success, data:', data);
setCases(data.cases || []);
} catch (err: any) {
console.error('❌ Error in fetchCases:', err);
setCasesError(err.message || 'Unknown error');
} finally {
setCasesLoading(false);
}
};
// Fetch cases when component mounts and session is authenticated
useEffect(() => {
if (status === 'authenticated' && session) {
fetchCases();
}
}, [status, session]);
const handleStatusUpdate = async (registrationToUpdate: Registration, newStatus: 'PENDING' | 'APPROVED' | 'REJECTED' | 'MISSING_DOCUMENTS' | 'DOCUMENTS_UNDER_REVIEW' | 'ADDITIONAL_INFO_NEEDED' | 'VERIFICATION_IN_PROGRESS' | 'LAWYER_VERIFICATION' | 'FACILITY_VERIFICATION' | 'DOCUMENTS_EXPIRED' | 'DOCUMENTS_INCOMPLETE' | 'INFORMATION_MISMATCH' | 'PENDING_PAYMENT' | 'PAYMENT_RECEIVED' | 'PENDING_LAWYER_APPROVAL' | 'PENDING_FACILITY_APPROVAL' | 'ON_HOLD' | 'ESCALATED' | 'FINAL_REVIEW' | 'COMPLETED' | 'WebAd') => {
try {
const response = await fetch(`/api/admin/registrations/${registrationToUpdate.id}/status`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: newStatus,
}),
});
if (!response.ok) {
let errorMessage = 'Failed to update status';
try {
const errorData = await response.json();
errorMessage = errorData.error || errorMessage;
} catch (parseError) {
// If we can't parse the error response, use status text
errorMessage = response.statusText || errorMessage;
}
throw new Error(errorMessage);
}
const updatedRegistration = await response.json();
setRegistrations(prev => prev.map(reg =>
reg.id === registrationToUpdate.id
? { ...reg, status: newStatus, updatedAt: updatedRegistration.updatedAt }
: reg
));
// Show success toast - auto-dismisses in 3 seconds
toast.success(`Status successfully updated to: ${getStatusLabel(newStatus)}`, {
duration: 3000,
position: 'top-right',
});
} catch (err) {
console.error('Error updating status:', err);
toast.error(`Failed to update status: ${err instanceof Error ? err.message : 'Unknown error'}`, {
duration: 4000,
position: 'top-right',
});
}
};
const handleDeleteRegistration = async (id: string, e: React.MouseEvent) => {
e.preventDefault();
if (!confirm('Are you sure you want to delete this registration?')) return;
try {
const response = await fetch(`/api/admin/registrations/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete registration');
}
setRegistrations(prev => prev.filter(reg => reg.id !== id));
toast.success('Registration deleted successfully', {
duration: 3000,
position: 'top-right',
});
} catch (err) {
console.error('Error deleting registration:', err);
toast.error('Failed to delete registration', {
duration: 4000,
position: 'top-right',
});
}
};
const handleRegistrationSuccess = () => {
setShowNewApplication(false);
// Refresh the registrations list
if (status === 'authenticated') {
const fetchRegistrations = async () => {
try {
setLoading(true);
const response = await fetch('/api/admin/registrations');
if (!response.ok) {
throw new Error('Failed to fetch registrations');
}
const data = await response.json();
setRegistrations(data);
setStats({
total: data.length,
pending: data.filter((r: Registration) => r.status === 'PENDING').length,
approved: data.filter((r: Registration) => r.status === 'APPROVED').length,
rejected: data.filter((r: Registration) => r.status === 'REJECTED').length,
webAd: data.filter((r: Registration) => r.status === 'WebAd').length
});
} catch (err) {
console.error('Error refreshing registrations:', err);
} finally {
setLoading(false);
}
};
fetchRegistrations();
}
};
// Show loading while checking authentication and role
if (status === 'loading' || loading) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto"></div>
<p className="mt-4 text-gray-600">
{status === 'loading' ? 'Checking authentication...' : 'Loading admin dashboard...'}
</p>
</div>
</div>
</LayoutWithSidebar>
);
}
// Don't render anything if not authenticated or not admin (redirects will handle this)
if (status === 'unauthenticated' || (status === 'authenticated' && !canAccessAdmin(session))) {
return null;
}
const filteredRegistrations = showWebAdOnly ? registrations.filter(r => r.status === 'WebAd') : registrations;
return (
<LayoutWithSidebar>
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
<div className="flex justify-between items-center">
<div>
<h1 className={`font-bold text-gray-900 dark:text-white ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{t.dashboard}</h1>
<p className="mt-2 text-gray-600 dark:text-gray-400">Manage all class action applications.</p>
</div>
<button
onClick={() => setShowNewApplication(true)}
className="bg-pink-600 hover:bg-pink-700 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>New Application</span>
</button>
</div>
</div>
{/* Debug Section - Only show in development */}
{process.env.NODE_ENV === 'development' && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">🔧 Debug Info</h3>
<div className="space-y-2 text-sm">
<p><strong>Current User:</strong> {session?.user?.name} ({session?.user?.email})</p>
<p><strong>Role:</strong> {session?.user?.role}</p>
<p><strong>Is Impersonating:</strong> {session?.user?.isImpersonating ? 'Yes' : 'No'}</p>
<p><strong>Can Access Cases:</strong> {['SUPERADMIN', 'ADMIN'].includes(session?.user?.role || '') ? 'Yes' : 'No'}</p>
{session?.user?.isImpersonating && (
<button
onClick={async () => {
try {
const response = await fetch('/api/admin/stop-impersonation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
window.location.reload();
}
} catch (error) {
console.error('Error stopping impersonation:', error);
}
}}
className="bg-red-500 text-white px-3 py-1 rounded text-sm hover:bg-red-600"
>
Stop Impersonation
</button>
)}
</div>
</div>
)}
{/* Beautiful Gradient Stats Cards */}
<div className={`grid gap-6 mb-8 ${isMobile ? 'grid-cols-2' : 'grid-cols-1 sm:grid-cols-3 lg:grid-cols-5'}`}>
{/* Total Users - Blue Gradient */}
<div className={`bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg text-white ${isMobile ? 'p-4' : 'p-6'} transform hover:scale-105 transition-all duration-300 border-l-4 border-blue-300`}>
<div className="flex items-center justify-between">
<div>
<h3 className={`font-semibold opacity-90 ${isMobile ? 'text-sm' : 'text-lg'}`}>{t.stats.total}</h3>
<p className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{stats.total}</p>
<p className="text-blue-100 text-sm">Total registered</p>
</div>
<div className="bg-white bg-opacity-20 p-3 rounded-full">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
</div>
</div>
{/* Pending - Orange Gradient */}
<div className={`bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl shadow-lg text-white ${isMobile ? 'p-4' : 'p-6'} transform hover:scale-105 transition-all duration-300 border-l-4 border-orange-300`}>
<div className="flex items-center justify-between">
<div>
<h3 className={`font-semibold opacity-90 ${isMobile ? 'text-sm' : 'text-lg'}`}>{t.stats.pending}</h3>
<p className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{stats.pending}</p>
<p className="text-orange-100 text-sm">Awaiting review</p>
</div>
<div className="bg-white bg-opacity-20 p-3 rounded-full">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
{/* Approved - Green Gradient */}
<div className={`bg-gradient-to-br from-green-500 to-green-600 rounded-xl shadow-lg text-white ${isMobile ? 'p-4' : 'p-6'} transform hover:scale-105 transition-all duration-300 border-l-4 border-green-300`}>
<div className="flex items-center justify-between">
<div>
<h3 className={`font-semibold opacity-90 ${isMobile ? 'text-sm' : 'text-lg'}`}>{t.stats.approved}</h3>
<p className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{stats.approved}</p>
<p className="text-green-100 text-sm">Successfully approved</p>
</div>
<div className="bg-white bg-opacity-20 p-3 rounded-full">
<svg className="w-6 h-6" 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>
{/* Rejected - Red Gradient */}
<div className={`bg-gradient-to-br from-red-500 to-red-600 rounded-xl shadow-lg text-white ${isMobile ? 'p-4' : 'p-6'} transform hover:scale-105 transition-all duration-300 border-l-4 border-red-300`}>
<div className="flex items-center justify-between">
<div>
<h3 className={`font-semibold opacity-90 ${isMobile ? 'text-sm' : 'text-lg'}`}>{t.stats.rejected}</h3>
<p className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{stats.rejected}</p>
<p className="text-red-100 text-sm">Need attention</p>
</div>
<div className="bg-white bg-opacity-20 p-3 rounded-full">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
{/* WebAd - Purple Gradient */}
<div className={`bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl shadow-lg text-white ${isMobile ? 'p-4' : 'p-6'} transform hover:scale-105 transition-all duration-300 border-l-4 border-purple-300`}>
<div className="flex items-center justify-between">
<div>
<h3 className={`font-semibold opacity-90 ${isMobile ? 'text-sm' : 'text-lg'}`}>WebAd</h3>
<p className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>{stats.webAd}</p>
<p className="text-purple-100 text-sm">Web applications</p>
</div>
<div className="bg-white bg-opacity-20 p-3 rounded-full">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9v-9m0-9v9" />
</svg>
</div>
</div>
</div>
</div>
{/* Filter Button */}
<div className={`mb-6 ${isMobile ? 'flex flex-col space-y-3' : 'flex justify-between items-center'}`}>
<h2 className={`font-semibold ${isMobile ? 'text-lg' : 'text-xl'}`}>All Registrations</h2>
<button
onClick={() => setShowWebAdOnly((v) => !v)}
className={`px-4 py-2 rounded-md font-semibold border transition flex items-center justify-center space-x-2 ${
showWebAdOnly ? 'bg-pink-600 text-white border-pink-600' : 'bg-white text-pink-700 border-pink-600 hover:bg-pink-50'
} ${isMobile ? 'w-full' : ''}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
<span>{showWebAdOnly ? 'Show All' : 'Show Only WebAd'}</span>
<span className={`${isMobile ? 'text-sm' : 'text-sm'} opacity-75`}>({stats.webAd})</span>
</button>
</div>
{/* Registrations Cards */}
{filteredRegistrations.length === 0 ? (
<div className="text-center py-12">
<div className="bg-gray-100 dark:bg-gray-800 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 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>
</div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">No Applications Found</h3>
<p className="text-gray-500 dark:text-gray-400">
{showWebAdOnly ? 'No WebAd applications found.' : 'No applications have been submitted yet.'}
</p>
</div>
) : (
<>
{/* Quick Actions */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Quick Actions</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<button
onClick={() => router.push('/admin/cases')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<svg className="h-6 w-6 text-blue-600 mr-3" 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 className="text-left">
<div className="font-medium text-gray-900">Case Management</div>
<div className="text-sm text-gray-500">Manage all cases</div>
</div>
</button>
<button
onClick={() => router.push('/admin/applications')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<svg className="h-6 w-6 text-purple-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<div className="text-left">
<div className="font-medium text-gray-900">Application Management</div>
<div className="text-sm text-gray-500">Manage all client applications</div>
</div>
</button>
<button
onClick={() => setShowNewApplication(true)}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<svg className="h-6 w-6 text-green-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<div className="text-left">
<div className="font-medium text-gray-900">Add New Application</div>
<div className="text-sm text-gray-500">Create registration</div>
</div>
</button>
<button
onClick={() => router.push('/admin/users')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<svg className="h-6 w-6 text-purple-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
</svg>
<div className="text-left">
<div className="font-medium text-gray-900">Manage Users</div>
<div className="text-sm text-gray-500">User administration</div>
</div>
</button>
<button
onClick={() => router.push('/unauthorized')}
className="flex items-center p-4 border border-red-200 rounded-lg hover:bg-red-50 transition-colors bg-red-50"
>
<svg className="h-6 w-6 text-red-600 mr-3" 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 className="text-left">
<div className="font-medium text-red-900">🚨 Test Security Page</div>
<div className="text-sm text-red-600">View unauthorized page</div>
</div>
</button>
</div>
</div>
{/* Unified Case Management Widget */}
<div className="mb-8">
<CaseWidget
title="All Cases Overview"
maxCases={8}
showCreateButton={true}
showViewAll={true}
showMyCases={false}
showAssignedCases={false}
showPublicCases={true}
showFilters={true}
className="mb-6"
/>
</div>
{/* Recent Activity */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Recent Activity</h2>
<div className="space-y-4">
<div className="flex items-center p-3 bg-blue-50 rounded-lg">
<svg className="h-5 w-5 text-blue-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<div>
<div className="font-medium text-gray-900">New Application Submitted</div>
<div className="text-sm text-gray-500">John Smith - Bordeaux Facility - 2 hours ago</div>
</div>
</div>
<div className="flex items-center p-3 bg-green-50 rounded-lg">
<svg className="h-5 w-5 text-green-600 mr-3" 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 className="font-medium text-gray-900">Application Approved</div>
<div className="text-sm text-gray-500">Sarah Johnson - Documents verified - 4 hours ago</div>
</div>
</div>
<div className="flex items-center p-3 bg-yellow-50 rounded-lg">
<svg className="h-5 w-5 text-yellow-600 mr-3" 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 className="font-medium text-gray-900">Document Review Required</div>
<div className="text-sm text-gray-500">Mike Davis - Missing identification - 6 hours ago</div>
</div>
</div>
<div className="flex items-center p-3 bg-purple-50 rounded-lg">
<svg className="h-5 w-5 text-purple-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div>
<div className="font-medium text-gray-900">System Notification Sent</div>
<div className="text-sm text-gray-500">Weekly update to all users - 1 day ago</div>
</div>
</div>
</div>
</div>
<div className={`grid gap-6 ${isMobile ? 'grid-cols-1' : 'grid-cols-1 md:grid-cols-2 xl:grid-cols-3'}`}>
{filteredRegistrations.map(registration => (
<div key={registration.id} className={`bg-white rounded-2xl shadow-xl flex flex-col border-2 ${isMobile ? 'p-4 min-h-[350px]' : 'p-6 min-h-[400px]'} ${
registration.status === 'WebAd' ? 'border-pink-200 bg-pink-50' : 'border-primary/10'
}`}>
{/* Status Icon & Badge */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<StatusIcon status={registration.status} />
<span className={`ml-3 px-3 py-1 rounded-full text-sm font-bold ${getStatusColor(registration.status)}`}>
{getStatusLabel(registration.status)}
</span>
</div>
{registration.status === 'WebAd' && (
<span className="px-2 py-1 rounded-full bg-pink-200 text-pink-800 text-xs font-bold">
Web Ad
</span>
)}
</div>
{/* Main Info */}
<div className="mb-3">
<div className={`font-bold ${isMobile ? 'text-lg' : 'text-xl'}`}>{registration.firstName} {registration.lastName}</div>
<div className="text-sm text-gray-500">{registration.email}</div>
<div className="text-sm text-gray-500">{registration.phone}</div>
</div>
{/* Detainee Info */}
<div className="mb-3">
<div className="text-sm">
<span className="font-semibold">Detainee:</span> {registration.detaineeInfo?.name || '-'}
</div>
<div className="text-sm text-gray-600">
{getFacilityName(registration.detaineeInfo?.facility || '') || '-'}
</div>
{registration.detaineeInfo?.inmateId && (
<div className="text-sm text-gray-600">ID: {registration.detaineeInfo.inmateId}</div>
)}
</div>
{/* Extra Info */}
<div className="mb-4">
<div className="text-xs text-gray-400 mb-2">
Created: {format(new Date(registration.createdAt), 'MMM d, yyyy')}
</div>
<div className="text-xs text-gray-400 mb-2">
Updated: {format(new Date(registration.updatedAt), 'MMM d, yyyy HH:mm')}
</div>
{registration.reasonForJoining && (
<div className="text-sm text-gray-700 italic mb-1">"{registration.reasonForJoining}"</div>
)}
{registration.urgentNeeds && (
<div className="text-sm text-pink-600 font-medium">{registration.urgentNeeds}</div>
)}
</div>
{/* Documents */}
{registration.documents && registration.documents.length > 0 && (
<div className="mb-4">
<h4 className="text-sm font-semibold mb-2">Documents ({registration.documents.length})</h4>
<div className="space-y-1">
{registration.documents.slice(0, 3).map((doc) => (
<div key={doc.id} className="flex items-center text-blue-600 text-sm">
<svg className="w-3 h-3 mr-1" 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>
{doc.name}
</div>
))}
{registration.documents.length > 3 && (
<div className="text-xs text-gray-500">+{registration.documents.length - 3} more</div>
)}
</div>
</div>
)}
{/* Status Dropdown */}
<div className="mt-4 bg-gray-50 p-3 rounded-lg border-2 border-dashed">
<label className="block text-sm font-semibold text-gray-700 mb-2">Quick Status Update</label>
<select
value={registration.status}
onChange={(e) => {
handleStatusUpdate(registration, e.target.value as any);
}}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary bg-white font-medium"
>
<option value="PENDING">⏳ Pending</option>
<option value="APPROVED">✅ Approved</option>
<option value="REJECTED">❌ Rejected</option>
<option value="MISSING_DOCUMENTS">📄 Missing Documents</option>
<option value="DOCUMENTS_UNDER_REVIEW">📋 Documents Under Review</option>
<option value="ADDITIONAL_INFO_NEEDED">ℹ️ Additional Info Needed</option>
<option value="VERIFICATION_IN_PROGRESS">🔍 Verification in Progress</option>
<option value="LAWYER_VERIFICATION">👨💼 Lawyer Verification</option>
<option value="FACILITY_VERIFICATION">🏢 Facility Verification</option>
<option value="DOCUMENTS_EXPIRED">⏰ Documents Expired</option>
<option value="DOCUMENTS_INCOMPLETE">📝 Documents Incomplete</option>
<option value="INFORMATION_MISMATCH">⚠️ Information Mismatch</option>
<option value="PENDING_PAYMENT">💰 Pending Payment</option>
<option value="PAYMENT_RECEIVED">💳 Payment Received</option>
<option value="PENDING_LAWYER_APPROVAL">👨💼 Pending Lawyer Approval</option>
<option value="PENDING_FACILITY_APPROVAL">🏢 Pending Facility Approval</option>
<option value="ON_HOLD">⏸️ On Hold</option>
<option value="ESCALATED">🚨 Escalated</option>
<option value="FINAL_REVIEW">🔍 Final Review</option>
<option value="COMPLETED">✅ Completed</option>
<option value="WebAd">🌐 Web Advertisement</option>
</select>
</div>
{/* Actions */}
<div className={`mt-auto pt-4 ${isMobile ? 'space-y-2' : 'grid grid-cols-4 gap-2'}`}>
<Link
href={`/admin/registrations/${registration.id}`}
className={`font-semibold text-sm px-4 py-2.5 rounded-lg flex items-center justify-center transition-all duration-200 shadow-md hover:shadow-lg transform hover:scale-105 text-white ${isMobile ? 'w-full' : ''}`}
style={{
background: `linear-gradient(135deg, #8b5cf6, #7c3aed)`,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #7c3aed, #8b5cf6)`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #8b5cf6, #7c3aed)`;
}}
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
View
</Link>
<Link
href={`/admin/registrations/${registration.id}?edit=true`}
className={`font-semibold text-sm px-4 py-2.5 rounded-lg flex items-center justify-center transition-all duration-200 shadow-md hover:shadow-lg transform hover:scale-105 text-white ${isMobile ? 'w-full' : ''}`}
style={{
background: `linear-gradient(135deg, #10b981, #059669)`,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #059669, #10b981)`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #10b981, #059669)`;
}}
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Edit
</Link>
<button
onClick={() => setShowPrivateChat({ open: true, registrationId: registration.id })}
className={`font-semibold text-sm px-4 py-2.5 rounded-lg flex items-center justify-center transition-all duration-200 shadow-md hover:shadow-lg transform hover:scale-105 text-white ${isMobile ? 'w-full' : ''}`}
style={{
background: `linear-gradient(135deg, #3b82f6, #2563eb)`,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #2563eb, #3b82f6)`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = `linear-gradient(135deg, #3b82f6, #2563eb)`;
}}
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
Chat
</button>
<button
onClick={(e) => handleDeleteRegistration(registration.id, e)}
className={`bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white font-semibold text-sm px-4 py-2.5 rounded-lg flex items-center justify-center transition-all duration-200 shadow-md hover:shadow-lg transform hover:scale-105 ${isMobile ? 'w-full' : ''}`}
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button>
</div>
</div>
))}
</div>
</>
)}
{/* Document Viewer Modal */}
<AnimatePresence>
{selectedDocument && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
>
<motion.div
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
className="bg-white rounded-lg shadow-xl max-w-7xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">{selectedDocument.name}</h2>
<button
onClick={() => setSelectedDocument(null)}
className="text-gray-500 hover:text-gray-700"
>
×
</button>
</div>
<DocumentViewer
url={selectedDocument.url}
type={selectedDocument.type}
name={selectedDocument.name}
/>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* Private Chat Modal */}
{showPrivateChat.open && showPrivateChat.registrationId && (
<PrivateChat
registrationId={showPrivateChat.registrationId}
onClose={() => setShowPrivateChat({ open: false, registrationId: null })}
/>
)}
{/* New Application Form */}
{showNewApplication && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Create New Application</h2>
<button
onClick={() => setShowNewApplication(false)}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
×
</button>
</div>
<RegistrationForm
onSuccess={handleRegistrationSuccess}
/>
</div>
</div>
</div>
)}
<section className="mt-8">
<h2 className="text-xl font-bold mb-2">All Cases</h2>
{casesLoading && <p>Loading cases...</p>}
{casesError && <p className="text-red-500">Error: {casesError}</p>}
{!casesLoading && !casesError && cases.length === 0 && <p>No cases found.</p>}
{!casesLoading && !casesError && cases.length > 0 && (
<table className="min-w-full border mt-2">
<thead>
<tr>
<th className="border px-2 py-1">Title</th>
<th className="border px-2 py-1">Status</th>
<th className="border px-2 py-1">Lead Lawyer</th>
<th className="border px-2 py-1">Created</th>
</tr>
</thead>
<tbody>
{cases.map((c) => (
<tr key={c.id}>
<td className="border px-2 py-1">{c.title}</td>
<td className="border px-2 py-1">{c.status}</td>
<td className="border px-2 py-1">{c.leadLawyerId || '-'}</td>
<td className="border px-2 py-1">{c.createdAt ? new Date(c.createdAt).toLocaleDateString() : '-'}</td>
</tr>
))}
</tbody>
</table>
)}
</section>
{/* Dashboard Modal */}
<DashboardModal
isOpen={isModalOpen}
onClose={closeModal}
route={currentRoute}
title={modalTitle}
/>
</div>
</LayoutWithSidebar>
);
};
export default AdminDashboard;