![]() 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/public_html/src/components/ |
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useSession } from 'next-auth/react';
import { formatDistanceToNow } from 'date-fns';
import MessageCenter from './MessageCenter';
import toast from 'react-hot-toast';
interface DashboardStats {
unreadMessages: number;
activeCases: number;
pendingTasks: number;
upcomingMeetings: number;
}
interface RecentActivity {
id: string;
type: 'message' | 'case_update' | 'meeting' | 'document';
title: string;
description: string;
timestamp: string;
priority?: 'low' | 'medium' | 'high' | 'urgent';
metadata?: {
senderId?: string;
senderName?: string;
caseId?: string;
caseTitle?: string;
};
}
interface EnhancedDashboardProps {
userRole: string;
userName: string;
userId: string;
children?: React.ReactNode;
}
const EnhancedDashboard: React.FC<EnhancedDashboardProps> = ({
userRole,
userName,
userId,
children
}) => {
const { data: session } = useSession();
const [stats, setStats] = useState<DashboardStats>({
unreadMessages: 0,
activeCases: 0,
pendingTasks: 0,
upcomingMeetings: 0
});
const [recentActivity, setRecentActivity] = useState<RecentActivity[]>([]);
const [loading, setLoading] = useState(true);
const [showMessageCenter, setShowMessageCenter] = useState(false);
const [showProfile, setShowProfile] = useState(false);
const [selectedProfileUserId, setSelectedProfileUserId] = useState<string | null>(null);
useEffect(() => {
fetchDashboardData();
// Set up real-time updates every 30 seconds
const interval = setInterval(fetchDashboardData, 30000);
return () => clearInterval(interval);
}, []);
const fetchDashboardData = async () => {
try {
const [statsResponse, activityResponse] = await Promise.all([
fetch('/api/dashboard/stats'),
fetch('/api/dashboard/activity')
]);
if (statsResponse.ok) {
const statsData = await statsResponse.json();
setStats(statsData);
}
if (activityResponse.ok) {
const activityData = await activityResponse.json();
setRecentActivity(activityData.activities || []);
}
} catch (error) {
console.error('Error fetching dashboard data:', error);
} finally {
setLoading(false);
}
};
const handleMessageClick = (messageId: string, senderId: string) => {
// Open team private chat or navigate to conversation
setShowMessageCenter(false);
toast.success('Opening conversation...');
// In real implementation, this would open the chat interface
};
const handleProfileClick = (userId: string) => {
setSelectedProfileUserId(userId);
setShowProfile(true);
};
const handleScheduleMeeting = (userId: string, userName: string) => {
toast.success(`Opening scheduler for ${userName}`);
// In real implementation, this would open a scheduling interface
};
const handleViewFullProfile = (userId: string) => {
window.open(`/profile/${userId}`, '_blank');
};
const getPriorityIcon = (priority?: string) => {
switch (priority) {
case 'urgent': return 'π΄';
case 'high': return 'π ';
case 'medium': return 'π‘';
default: return 'π΅';
}
};
const getActivityIcon = (type: string) => {
switch (type) {
case 'message': return 'π¬';
case 'case_update': return 'βοΈ';
case 'meeting': return 'π
';
case 'document': return 'π';
default: return 'π';
}
};
const getRoleGreeting = (role: string) => {
switch (role) {
case 'SUPERADMIN':
case 'SUPERADMIN':
case 'SUPERADMIN': return 'Legal Representative & CEO Dashboard';
case 'ADMIN': return 'Legal Team Lead Dashboard';
case 'LAWYER': return 'Attorney Dashboard';
case 'SECRETARY': return 'Legal Secretary Dashboard';
case 'ASSISTANT': return 'Legal Assistant Dashboard';
case 'CLERK': return 'Legal Clerk Dashboard';
default: return 'Client Portal';
}
};
const getWelcomeMessage = () => {
const hour = new Date().getHours();
let timeGreeting = '';
if (hour < 12) timeGreeting = 'Good morning';
else if (hour < 17) timeGreeting = 'Good afternoon';
else timeGreeting = 'Good evening';
return `${timeGreeting}, ${userName.split(' ')[0]}`;
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
{/* Enhanced Header */}
<div className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo and Title */}
<div className="flex items-center space-x-4">
<div className="flex-shrink-0">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
LibertΓ© MΓͺme En Prison
</h1>
</div>
<div className="hidden md:block">
<div className="text-sm text-gray-500 dark:text-gray-400">
{getRoleGreeting(userRole)}
</div>
</div>
</div>
{/* Quick Actions */}
<div className="flex items-center space-x-4">
{/* Messages Button */}
<button
onClick={() => setShowMessageCenter(true)}
className="relative p-2 text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 transition-colors rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
title="Messages"
>
<svg className="w-6 h-6" 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-3.582 8-8 8a8.001 8.001 0 01-7.716-6M3 12a9 9 0 1118 0v0z" />
</svg>
{stats.unreadMessages > 0 && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-medium"
>
{stats.unreadMessages > 99 ? '99+' : stats.unreadMessages}
</motion.div>
)}
</button>
{/* Profile Button */}
<button
onClick={() => handleProfileClick(userId)}
className="flex items-center space-x-2 p-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
title="Your Profile"
>
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-semibold text-sm">
{userName.charAt(0).toUpperCase()}
</div>
<span className="hidden md:block text-sm font-medium">{userName.split(' ')[0]}</span>
</button>
</div>
</div>
</div>
</div>
{/* Welcome Section */}
<div className="bg-gradient-to-r from-blue-600 via-purple-600 to-indigo-700 text-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold">{getWelcomeMessage()}</h2>
<p className="text-blue-100 mt-1">
Here's what's happening with your legal matters today
</p>
</div>
<div className="hidden lg:block">
<div className="text-right">
<div className="text-2xl font-bold">
{new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</div>
<div className="text-blue-200 text-sm">
{new Date().toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
</div>
</div>
</div>
</div>
{/* Dashboard Stats */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{[
{
title: 'Unread Messages',
value: stats.unreadMessages,
icon: 'π¬',
color: 'from-blue-500 to-blue-600',
action: () => setShowMessageCenter(true),
urgent: stats.unreadMessages > 0
},
{
title: 'Active Cases',
value: stats.activeCases,
icon: 'βοΈ',
color: 'from-purple-500 to-purple-600',
action: () => {},
urgent: false
},
{
title: 'Pending Tasks',
value: stats.pendingTasks,
icon: 'π',
color: 'from-green-500 to-green-600',
action: () => {},
urgent: stats.pendingTasks > 5
},
{
title: 'Meetings Today',
value: stats.upcomingMeetings,
icon: 'π
',
color: 'from-orange-500 to-orange-600',
action: () => {},
urgent: stats.upcomingMeetings > 0
}
].map((stat, index) => (
<motion.div
key={stat.title}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className={`relative bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden cursor-pointer transition-all duration-200 hover:shadow-xl ${
stat.urgent ? 'ring-2 ring-red-400 ring-opacity-75' : ''
}`}
onClick={stat.action}
>
<div className={`absolute inset-0 bg-gradient-to-br ${stat.color} opacity-5`}></div>
<div className="relative p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">
{stat.title}
</p>
<p className={`text-3xl font-bold mt-2 ${
stat.urgent ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white'
}`}>
{stat.value}
</p>
</div>
<div className="text-4xl opacity-80">
{stat.icon}
</div>
</div>
{stat.urgent && (
<div className="mt-3">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 animate-pulse">
Needs Attention
</span>
</div>
)}
</div>
</motion.div>
))}
</div>
</div>
{/* Main Content Area */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Recent Activity */}
<div className="lg:col-span-2">
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
π Recent Activity
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Stay updated with the latest developments
</p>
</div>
<div className="p-6">
{loading ? (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="animate-pulse flex space-x-4">
<div className="rounded-full bg-gray-300 h-10 w-10"></div>
<div className="flex-1 space-y-2 py-1">
<div className="h-4 bg-gray-300 rounded w-3/4"></div>
<div className="h-3 bg-gray-300 rounded w-1/2"></div>
</div>
</div>
))}
</div>
) : recentActivity.length === 0 ? (
<div className="text-center py-8">
<div className="text-5xl mb-4">π</div>
<p className="text-gray-500 font-medium">No recent activity</p>
<p className="text-sm text-gray-400 mt-1">Activity will appear here as it happens</p>
</div>
) : (
<div className="space-y-4">
{recentActivity.slice(0, 10).map((activity) => (
<motion.div
key={activity.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="flex items-start space-x-4 p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer"
onClick={() => {
if (activity.type === 'message' && activity.metadata?.senderId) {
handleProfileClick(activity.metadata.senderId);
}
}}
>
<div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white text-lg">
{getActivityIcon(activity.type)}
</div>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{activity.title}
</p>
<div className="flex items-center space-x-2">
{activity.priority && (
<span className="text-sm">
{getPriorityIcon(activity.priority)}
</span>
)}
<span className="text-xs text-gray-400">
{formatDistanceToNow(new Date(activity.timestamp), { addSuffix: true })}
</span>
</div>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1">
{activity.description}
</p>
{activity.metadata?.caseTitle && (
<div className="mt-2">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
βοΈ {activity.metadata.caseTitle}
</span>
</div>
)}
</div>
</motion.div>
))}
</div>
)}
</div>
</div>
</div>
{/* Quick Actions & Info */}
<div className="space-y-6">
{/* Quick Actions */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
β‘ Quick Actions
</h3>
</div>
<div className="p-6 space-y-3">
<button
onClick={() => setShowMessageCenter(true)}
className="w-full flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
>
<span className="flex items-center gap-2">
<span>π¬</span>
<span>View Messages</span>
</span>
{stats.unreadMessages > 0 && (
<span className="bg-blue-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{stats.unreadMessages}
</span>
)}
</button>
<button className="w-full flex items-center gap-2 p-3 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-lg hover:bg-green-100 dark:hover:bg-green-900/30 transition-colors">
<span>π
</span>
<span>Schedule Meeting</span>
</button>
<button className="w-full flex items-center gap-2 p-3 bg-purple-50 dark:bg-purple-900/20 text-purple-700 dark:text-purple-300 rounded-lg hover:bg-purple-100 dark:hover:bg-purple-900/30 transition-colors">
<span>π</span>
<span>Upload Document</span>
</button>
<button className="w-full flex items-center gap-2 p-3 bg-orange-50 dark:bg-orange-900/20 text-orange-700 dark:text-orange-300 rounded-lg hover:bg-orange-100 dark:hover:bg-orange-900/30 transition-colors">
<span>βοΈ</span>
<span>Case Management</span>
</button>
</div>
</div>
{/* System Status */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
π’ System Status
</h3>
</div>
<div className="p-6 space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600 dark:text-gray-400">Chat System</span>
<span className="flex items-center gap-1 text-green-600 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
Online
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600 dark:text-gray-400">Case Management</span>
<span className="flex items-center gap-1 text-green-600 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
Active
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600 dark:text-gray-400">Document Storage</span>
<span className="flex items-center gap-1 text-green-600 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
Operational
</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Original Dashboard Content */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{children}
</div>
{/* Message Center Modal */}
<MessageCenter
isOpen={showMessageCenter}
onClose={() => setShowMessageCenter(false)}
onMessageClick={handleMessageClick}
/>
{/* Modern Profile Modal - Component removed */}
{selectedProfileUserId && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">User Profile</h3>
<button
onClick={() => {
setShowProfile(false);
setSelectedProfileUserId(null);
}}
className="text-gray-400 hover:text-gray-600"
>
β
</button>
</div>
<p className="text-gray-600 dark:text-gray-400">Profile view coming soon...</p>
</div>
</div>
)}
</div>
);
};
export default EnhancedDashboard;