![]() 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/components/ |
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
interface TeamMember {
id: string;
name: string;
role: string;
avatar?: string;
isOnline: boolean;
lastSeen: Date;
currentTask?: string;
}
interface CaseActivity {
id: string;
type: 'document_upload' | 'status_change' | 'comment' | 'assignment' | 'deadline';
description: string;
user: TeamMember;
timestamp: Date;
caseId: string;
}
interface CollaborationHubProps {
caseId?: string;
}
const CollaborationHub: React.FC<CollaborationHubProps> = ({ caseId }) => {
const { data: session } = useSession();
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
const [activities, setActivities] = useState<CaseActivity[]>([]);
const [activeUsers, setActiveUsers] = useState<string[]>([]);
const [selectedView, setSelectedView] = useState<'team' | 'activity' | 'tasks'>('team');
useEffect(() => {
if (caseId) {
fetchTeamMembers();
fetchActivities();
setupRealTimeUpdates();
}
}, [caseId]);
const fetchTeamMembers = async () => {
try {
const response = await fetch(`/api/cases/${caseId}/team`);
if (response.ok) {
const data = await response.json();
setTeamMembers(data);
}
} catch (error) {
console.error('Error fetching team members:', error);
}
};
const fetchActivities = async () => {
try {
const response = await fetch(`/api/cases/${caseId}/activities`);
if (response.ok) {
const data = await response.json();
setActivities(data);
}
} catch (error) {
console.error('Error fetching activities:', error);
}
};
const setupRealTimeUpdates = () => {
// WebSocket connection for real-time updates
const ws = new WebSocket(`${process.env.NEXT_PUBLIC_WS_URL}/case/${caseId}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'user_online':
setActiveUsers(prev => [...prev, data.userId]);
break;
case 'user_offline':
setActiveUsers(prev => prev.filter(id => id !== data.userId));
break;
case 'activity_update':
setActivities(prev => [data.activity, ...prev]);
break;
case 'team_update':
fetchTeamMembers();
break;
}
};
return () => ws.close();
};
const getActivityIcon = (type: string) => {
switch (type) {
case 'document_upload': return '📄';
case 'status_change': return '🔄';
case 'comment': return '💬';
case 'assignment': return '👤';
case 'deadline': return '⏰';
default: return '📝';
}
};
const getTimeAgo = (date: Date) => {
const now = new Date();
const diff = now.getTime() - date.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'Just now';
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
return `${days}d ago`;
};
return (
<div className="bg-white shadow-lg rounded-lg h-96 flex flex-col">
{/* Header */}
<div className="border-b border-gray-200 p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-gray-900">🤝 Collaboration Hub</h3>
<div className="flex items-center gap-2">
<div className="flex -space-x-2">
{teamMembers.slice(0, 3).map((member) => (
<div
key={member.id}
className="relative w-8 h-8 bg-gradient-to-r from-primary to-primary-dark rounded-full flex items-center justify-center text-white text-sm font-semibold border-2 border-white"
title={member.name}
>
{member.name.charAt(0).toUpperCase()}
{activeUsers.includes(member.id) && (
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white"></div>
)}
</div>
))}
{teamMembers.length > 3 && (
<div className="w-8 h-8 bg-gray-300 rounded-full flex items-center justify-center text-gray-600 text-xs font-semibold border-2 border-white">
+{teamMembers.length - 3}
</div>
)}
</div>
</div>
</div>
{/* Tab Navigation */}
<nav className="flex space-x-1">
{[
{ id: 'team', label: '👥 Team', count: teamMembers.length },
{ id: 'activity', label: '📊 Activity', count: activities.length },
{ id: 'tasks', label: '✅ Tasks', count: 0 }
].map((tab) => (
<button
key={tab.id}
onClick={() => setSelectedView(tab.id as any)}
className={`px-3 py-1 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} ({tab.count})
</button>
))}
</nav>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
{/* Team View */}
{selectedView === 'team' && (
<div className="space-y-3">
{teamMembers.map((member) => (
<div key={member.id} className="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:bg-gray-50">
<div className="flex items-center gap-3">
<div className="relative">
<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">
{member.name.charAt(0).toUpperCase()}
</div>
{activeUsers.includes(member.id) ? (
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" title="Online"></div>
) : (
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-gray-400 rounded-full border-2 border-white" title="Offline"></div>
)}
</div>
<div>
<h4 className="font-medium text-gray-900">{member.name}</h4>
<p className="text-sm text-gray-600">{member.role}</p>
{member.currentTask && (
<p className="text-xs text-blue-600">🔄 {member.currentTask}</p>
)}
</div>
</div>
<div className="text-right">
<div className={`px-2 py-1 rounded text-xs font-medium ${
activeUsers.includes(member.id)
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-600'
}`}>
{activeUsers.includes(member.id) ? 'Online' : getTimeAgo(member.lastSeen)}
</div>
</div>
</div>
))}
</div>
)}
{/* Activity Feed */}
{selectedView === 'activity' && (
<div className="space-y-3">
{activities.map((activity) => (
<div key={activity.id} className="flex items-start gap-3 p-3 border border-gray-200 rounded-lg">
<div className="text-2xl">{getActivityIcon(activity.type)}</div>
<div className="flex-1">
<p className="text-sm text-gray-900">{activity.description}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-gray-600">{activity.user.name}</span>
<span className="text-xs text-gray-400">•</span>
<span className="text-xs text-gray-600">{getTimeAgo(activity.timestamp)}</span>
</div>
</div>
</div>
))}
{activities.length === 0 && (
<div className="text-center py-8 text-gray-500">
<div className="text-4xl mb-2">📝</div>
<p>No recent activity</p>
</div>
)}
</div>
)}
{/* Tasks View */}
{selectedView === 'tasks' && (
<div className="space-y-3">
<div className="text-center py-8 text-gray-500">
<div className="text-4xl mb-2">✅</div>
<p>Task management coming soon</p>
<p className="text-sm">Track assignments, deadlines, and progress</p>
</div>
</div>
)}
</div>
{/* Quick Actions Footer */}
<div className="border-t border-gray-200 p-3">
<div className="flex gap-2">
<button className="flex-1 px-3 py-2 text-sm bg-primary text-white rounded-md hover:bg-primary-dark transition-colors">
💬 Quick Chat
</button>
<button className="flex-1 px-3 py-2 text-sm bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors">
📋 Add Task
</button>
<button className="px-3 py-2 text-sm bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors">
⚙️
</button>
</div>
</div>
</div>
);
};
export default CollaborationHub;