'use client'; import { routes } from '@/lib/routing/RouteConfig'; import { Badge } from '@/ui/Badge'; import { CountryFlag } from '@/ui/CountryFlag'; import { Icon } from '@/ui/Icon'; import { Image } from '@/ui/Image'; import { Link } from '@/ui/Link'; import { PlaceholderImage } from '@/ui/PlaceholderImage'; import { Stack } from '@/ui/Stack'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table'; import { Text } from '@/ui/Text'; import { Edit, User } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; // League role display data const leagueRoleDisplay = { owner: { text: 'Owner', bg: 'bg-yellow-500/10', color: 'text-yellow-500', borderColor: 'border-yellow-500/30', }, admin: { text: 'Admin', bg: 'bg-purple-500/10', color: 'text-purple-400', borderColor: 'border-purple-500/30', }, steward: { text: 'Steward', bg: 'bg-blue-500/10', color: 'text-blue-400', borderColor: 'border-blue-500/30', }, member: { text: 'Member', bg: 'bg-primary-blue/10', color: 'text-primary-blue', borderColor: 'border-primary-blue/30', }, } as const; // Position background colors const getPositionBgColor = (position: number) => { switch (position) { case 1: return { bg: 'bg-yellow-500/10', borderLeft: true, borderColor: 'border-l-yellow-500' }; case 2: return { bg: 'bg-gray-300/10', borderLeft: true, borderColor: 'border-l-gray-400' }; case 3: return { bg: 'bg-amber-600/10', borderLeft: true, borderColor: 'border-l-amber-600' }; default: return { borderLeft: true, borderColor: 'border-l-transparent' }; } }; interface StandingsTableProps { standings: Array<{ driverId: string; position: number; positionLabel: string; totalPointsLabel: string; racesLabel: string; avgFinishLabel: string; penaltyPointsLabel: string; bonusPointsLabel: string; teamName?: string; }>; drivers: Array<{ id: string; name: string; avatarUrl: string | null; iracingId?: string; rating?: number; country?: string; }>; memberships?: Array<{ driverId: string; role: 'owner' | 'admin' | 'steward' | 'member'; joinedAt: string; status: 'active' | 'pending' | 'banned'; }>; currentDriverId?: string; isAdmin?: boolean; onRemoveMember?: (driverId: string) => void; onUpdateRole?: (driverId: string, role: string) => void; } export function StandingsTable({ standings, drivers, memberships = [], currentDriverId, isAdmin = false, onRemoveMember, onUpdateRole }: StandingsTableProps) { const [hoveredRow, setHoveredRow] = useState(null); const [activeMenu, setActiveMenu] = useState<{ driverId: string; type: 'member' | 'points' } | null>(null); const menuRef = useRef(null); // Close menu when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(event.target as Node)) { setActiveMenu(null); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const getDriver = (driverId: string) => { return drivers.find((d) => d.id === driverId); }; const getMembership = (driverId: string) => { return memberships.find((m) => m.driverId === driverId); }; const canModifyMember = (driverId: string): boolean => { if (!isAdmin) return false; if (driverId === currentDriverId) return false; const membership = getMembership(driverId); // Allow managing drivers even without formal membership (they have standings = they're participating) // But don't allow modifying the owner if (membership && membership.role === 'owner') return false; return true; }; const isCurrentUser = (driverId: string): boolean => { return driverId === currentDriverId; }; type MembershipRole = string; const handleRoleChange = (driverId: string, newRole: MembershipRole) => { if (!onUpdateRole) return; const membership = getMembership(driverId); if (!membership) return; const confirmationMessages: Record = { owner: 'Cannot promote to owner', admin: 'Promote this member to Admin? They will have full management permissions.', steward: 'Assign Steward role? They will be able to manage protests and penalties.', member: 'Demote to regular Member? They will lose elevated permissions.' }; if (newRole === 'owner') { alert(confirmationMessages.owner); return; } if (newRole !== membership.role && confirm(confirmationMessages[newRole])) { onUpdateRole(driverId, newRole); setActiveMenu(null); } }; const handleRemove = (driverId: string) => { if (!onRemoveMember) return; const driver = getDriver(driverId); const driverName = driver?.name || 'this member'; if (confirm(`Remove ${driverName} from the league? This action cannot be undone.`)) { onRemoveMember(driverId); setActiveMenu(null); } }; const MemberActionMenu = ({ driverId }: { driverId: string }) => { const membership = getMembership(driverId); // For drivers without membership, show limited options (add as member, remove from standings) const hasMembership = !!membership; return ( e.stopPropagation()} > Member Management {hasMembership ? ( <> {/* Role Management for existing members */} {membership!.role !== 'admin' && membership!.role !== 'owner' && ( { e.stopPropagation(); handleRoleChange(driverId, 'admin'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-white" hoverBg="bg-purple-500/10" transition > 🛡️ Promote to Admin )} {membership!.role === 'admin' && ( { e.stopPropagation(); handleRoleChange(driverId, 'member'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-white" hoverBg="bg-iron-gray/20" transition > ⬇️ Demote to Member )} {membership!.role === 'member' && ( { e.stopPropagation(); handleRoleChange(driverId, 'steward'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-white" hoverBg="bg-blue-500/10" transition > 🏁 Make Steward )} {membership!.role === 'steward' && ( { e.stopPropagation(); handleRoleChange(driverId, 'member'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-white" hoverBg="bg-iron-gray/20" transition > 🏁 Remove Steward )} { e.stopPropagation(); handleRemove(driverId); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-red-400" hoverBg="bg-red-500/10" transition > 🚫 Remove from League ) : ( <> {/* Options for drivers without membership (participating but not formal members) */} Driver not a formal member { e.stopPropagation(); alert('Add as member - feature coming soon'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-white" hoverBg="bg-green-500/10" transition > Add as Member { e.stopPropagation(); handleRemove(driverId); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" fontSize="14px" color="text-red-400" hoverBg="bg-red-500/10" transition > 🚫 Remove from Standings )} ); }; const PointsActionMenu = () => { return ( e.stopPropagation()} > Score Actions { e.stopPropagation(); alert('View detailed stats - feature coming soon'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" color="text-white" hoverBg="bg-iron-gray/20" transition fontSize="14px" > 📊 View Details { e.stopPropagation(); alert('Manual adjustment - feature coming soon'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" color="text-white" hoverBg="bg-iron-gray/20" transition fontSize="14px" > ⚠️ Adjust Points { e.stopPropagation(); alert('Race history - feature coming soon'); }} display="flex" alignItems="center" gap={2} w="full" textAlign="left" px={3} py={2} rounded="md" color="text-white" hoverBg="bg-iron-gray/20" transition fontSize="14px" > 📝 Race History ); }; if (standings.length === 0) { return ( No standings available ); } return ( Pos Driver Team Points Races Avg Finish Penalty Bonus {standings.map((row) => { const driver = getDriver(row.driverId); const membership = getMembership(row.driverId); const roleDisplay = membership ? leagueRoleDisplay[membership.role] : null; const canModify = canModifyMember(row.driverId); const isRowHovered = hoveredRow === row.driverId; const isMemberMenuOpen = activeMenu?.driverId === row.driverId && activeMenu?.type === 'member'; const isPointsMenuOpen = activeMenu?.driverId === row.driverId && activeMenu?.type === 'points'; const isMe = isCurrentUser(row.driverId); const posConfig = getPositionBgColor(row.position); return ( setHoveredRow(row.driverId)} onMouseLeave={() => { setHoveredRow(null); if (!isMemberMenuOpen && !isPointsMenuOpen) { setActiveMenu(null); } }} > {/* Position */} {row.positionLabel} {/* Driver with Rating and Nationality */} {/* Avatar */} {driver && ( driver.avatarUrl ? ( ) : ( ) )} {/* Nationality flag */} {driver && driver.country && ( )} {/* Name and Rating */} {driver?.name || 'Unknown Driver'} {isMe && ( You )} {roleDisplay && roleDisplay.text !== 'Member' && ( {roleDisplay.text} )} {/* Hover Actions for Member Management */} {isAdmin && canModify && ( { e.stopPropagation(); setActiveMenu(isMemberMenuOpen ? null : { driverId: row.driverId, type: 'member' }); }} p={1.5} rounded="md" bg={isMemberMenuOpen ? 'bg-primary-blue/20' : ''} color={isMemberMenuOpen ? 'text-primary-blue' : 'text-gray-400'} hoverColor={!isMemberMenuOpen ? 'text-white' : ''} hoverBg={!isMemberMenuOpen ? 'bg-iron-gray/30' : ''} transition title="Manage member" > )} {isMemberMenuOpen && } {/* Team */} {row.teamName ?? '—'} {/* Total Points with Hover Action */} {row.totalPointsLabel} {isAdmin && canModify && ( { e.stopPropagation(); setActiveMenu(isPointsMenuOpen ? null : { driverId: row.driverId, type: 'points' }); }} p={1} rounded="md" bg={isPointsMenuOpen ? 'bg-primary-blue/20' : ''} color={isPointsMenuOpen ? 'text-primary-blue' : 'text-gray-400'} hoverColor={!isPointsMenuOpen ? 'text-white' : ''} hoverBg={!isPointsMenuOpen ? 'bg-iron-gray/30' : ''} transition opacity={isRowHovered || isPointsMenuOpen ? 1 : 0} visibility={isRowHovered || isPointsMenuOpen ? 'visible' : 'hidden'} title="Score actions" > )} {isPointsMenuOpen && } {/* Races (Finished/Started) */} {row.racesLabel} {/* Avg Finish */} {row.avgFinishLabel} {/* Penalty */} {row.penaltyPointsLabel} {/* Bonus */} {row.bonusPointsLabel} ); })}
); }