![]() 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/pages/ |
import React, { useState, useEffect } from 'react';
import LayoutWithSidebar from '../components/LayoutWithSidebar';
import Head from 'next/head';
import { useSession } from 'next-auth/react';
import {
Users,
Building,
Scale,
FileText,
Calendar,
Clock,
DollarSign,
TrendingUp,
Shield,
Award,
Star,
CheckCircle,
AlertTriangle,
Eye,
Edit,
Plus,
Search,
Filter,
Download,
Mail,
Phone,
Globe,
MapPin,
User,
Settings,
Bell,
Home,
Briefcase,
BookOpen,
GraduationCap,
Trophy,
Target,
Database,
Zap,
Brain,
Rocket,
Crown,
Heart,
Lightbulb,
BarChart3,
PieChart,
LineChart,
Activity,
ArrowUp,
ArrowDown,
ArrowRight,
ArrowLeft,
ChevronUp,
ChevronDown,
ChevronRight,
ChevronLeft,
Minus,
X,
Check,
Lock,
Unlock,
Key,
EyeOff,
Trash2,
Copy,
Share,
ExternalLink,
Link as LinkIcon,
Upload,
Save,
RefreshCw,
RotateCcw,
RotateCw,
ZoomIn,
ZoomOut,
Maximize,
Minimize,
Move,
Grid,
List,
Columns,
Rows,
Layout,
Menu,
MoreHorizontal,
MoreVertical,
Circle,
Square,
Triangle,
Hexagon,
Octagon,
ThumbsUp,
ThumbsDown,
Flag,
Bookmark,
Tag,
Hash,
AtSign,
Percent,
} from 'lucide-react';
import toast from 'react-hot-toast';
interface SocietyMember {
id: string;
name: string;
email: string;
role: string;
xp: number;
currentDegree: {
number: number;
name: string;
title: string;
symbol: string;
color: string;
lodgeLevel: string;
} | null;
lodge: {
name: string;
level: string;
} | null;
joinedDate: string;
}
interface SocietyStats {
totalMembers: number;
totalXP: number;
averageDegree: number;
totalCases: number;
recentAdvancements: number;
lodgeBreakdown: Array<{
name: string;
level: string;
members: number;
}>;
}
const SocietyDashboard: React.FC = () => {
const { data: session } = useSession();
const [members, setMembers] = useState<SocietyMember[]>([]);
const [stats, setStats] = useState<SocietyStats | null>(null);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [selectedLodge, setSelectedLodge] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
fetchSocietyData();
}, [currentPage, searchTerm, selectedLodge]);
const fetchSocietyData = async () => {
try {
setLoading(true);
// Fetch dashboard stats
const statsResponse = await fetch('/api/society/dashboard');
if (statsResponse.ok) {
const statsData = await statsResponse.json();
setStats(statsData.stats);
}
// Fetch members with pagination and filters
const params = new URLSearchParams({
page: currentPage.toString(),
limit: '20'
});
if (searchTerm) params.append('search', searchTerm);
if (selectedLodge) params.append('lodge', selectedLodge);
const membersResponse = await fetch(`/api/society/members?${params}`);
if (membersResponse.ok) {
const membersData = await membersResponse.json();
setMembers(membersData.members);
setTotalPages(membersData.pagination.pages);
}
} catch (error) {
console.error('Error fetching society data:', error);
toast.error('Failed to load society data');
} finally {
setLoading(false);
}
};
const getDegreeColor = (degree: number) => {
if (degree >= 30) return 'text-purple-600 bg-purple-100';
if (degree >= 20) return 'text-red-600 bg-red-100';
if (degree >= 10) return 'text-blue-600 bg-blue-100';
return 'text-green-600 bg-green-100';
};
const getLodgeColor = (lodge: string) => {
switch (lodge) {
case 'BLACK': return 'bg-gray-800 text-white';
case 'RED': return 'bg-red-600 text-white';
case 'BLUE': return 'bg-blue-600 text-white';
default: return 'bg-gray-600 text-white';
}
};
const filteredMembers = members.filter(member => {
if (searchTerm && !member.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return false;
}
if (selectedLodge && member.lodge?.level !== selectedLodge) {
return false;
}
return true;
});
if (loading) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
</div>
</LayoutWithSidebar>
);
}
return (
<>
<Head>
<title>Society Dashboard - Liberté Même En Prison</title>
<meta name="description" content="Society of Brothers Dashboard" />
</Head>
<LayoutWithSidebar>
<div className="min-h-screen bg-gray-50 p-6">
{/* Header */}
<div className="bg-gradient-to-r from-blue-900 via-purple-900 to-blue-900 text-white rounded-2xl p-8 mb-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-4xl font-bold mb-2">🏛️ Society of Brothers</h1>
<p className="text-xl opacity-90">33 Degrees of Legal Mastery</p>
</div>
<div className="text-right">
<div className="text-3xl font-bold">{stats?.totalMembers || 0}</div>
<div className="text-sm opacity-75">Active Members</div>
</div>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg">
<Users className="h-6 w-6 text-blue-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total Members</p>
<p className="text-2xl font-bold text-gray-900">{stats?.totalMembers || 0}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="flex items-center">
<div className="p-2 bg-purple-100 rounded-lg">
<Trophy className="h-6 w-6 text-purple-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total XP</p>
<p className="text-2xl font-bold text-gray-900">
{stats?.totalXP.toLocaleString() || 0}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="flex items-center">
<div className="p-2 bg-green-100 rounded-lg">
<Star className="h-6 w-6 text-green-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Average Degree</p>
<p className="text-2xl font-bold text-gray-900">
{stats?.averageDegree || 1}°
</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="flex items-center">
<div className="p-2 bg-red-100 rounded-lg">
<FileText className="h-6 w-6 text-red-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total Cases</p>
<p className="text-2xl font-bold text-gray-900">
{stats?.totalCases.toLocaleString() || 0}
</p>
</div>
</div>
</div>
</div>
{/* Lodge Breakdown */}
{stats?.lodgeBreakdown && (
<div className="bg-white rounded-xl p-6 shadow-lg mb-8">
<h2 className="text-xl font-bold text-gray-900 mb-4">Lodge Distribution</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{stats.lodgeBreakdown.map((lodge) => (
<div key={lodge.name} className="text-center p-4 rounded-lg border">
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium mb-2 ${getLodgeColor(lodge.level)}`}>
{lodge.name}
</div>
<p className="text-2xl font-bold text-gray-900">{lodge.members}</p>
<p className="text-sm text-gray-600">Members</p>
</div>
))}
</div>
</div>
)}
{/* Search and Filters */}
<div className="bg-white rounded-xl p-6 shadow-lg mb-8">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="h-5 w-5 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<input
type="text"
placeholder="Search members..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div className="md:w-48">
<select
value={selectedLodge}
onChange={(e) => setSelectedLodge(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">All Lodges</option>
<option value="BLUE">Blue Lodge</option>
<option value="RED">Red Lodge</option>
<option value="BLACK">Black Lodge</option>
</select>
</div>
</div>
</div>
{/* Members Table */}
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">Society Members</h2>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Member
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Degree
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lodge
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
XP
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Joined
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredMembers.map((member) => (
<tr key={member.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center">
<User className="h-6 w-6 text-blue-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{member.name}</div>
<div className="text-sm text-gray-500">{member.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{member.currentDegree ? (
<div className="flex items-center">
<span className="text-2xl mr-2">{member.currentDegree.symbol}</span>
<div>
<div className="text-sm font-medium text-gray-900">
{member.currentDegree.number}° {member.currentDegree.name}
</div>
<div className="text-sm text-gray-500">{member.currentDegree.title}</div>
</div>
</div>
) : (
<span className="text-gray-400">No degree</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{member.lodge ? (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getLodgeColor(member.lodge.level)}`}>
{member.lodge.name}
</span>
) : (
<span className="text-gray-400">No lodge</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{member.xp.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(member.joinedDate).toLocaleDateString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="px-6 py-4 border-t border-gray-200">
<div className="flex items-center justify-between">
<div className="text-sm text-gray-700">
Page {currentPage} of {totalPages}
</div>
<div className="flex space-x-2">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
Previous
</button>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
Next
</button>
</div>
</div>
</div>
)}
</div>
</div>
</LayoutWithSidebar>
</>
);
};
export default SocietyDashboard;