import React, { useEffect } from 'react'; import { Lock, LogIn, ShieldAlert, UserCheck, Clock, Zap, MessageSquare, Award, Shield } from 'lucide-react'; import { Radar } from 'react-chartjs-2'; // Custom SVG Circular Progress Component function CircularProgress({ percentage, size = 68, strokeWidth = 5, color = 'var(--color-brand)' }) { const radius = (size - strokeWidth) / 2; const circumference = radius * 2 * Math.PI; const offset = circumference - (percentage / 100) * circumference; return (
{/* Background Circle */} {/* Foreground Circle */}
{percentage}%
); } // Custom Initials Avatar Generator function InitialsAvatar({ name, size = 40, border = '1px solid var(--color-border)' }) { const initials = (name || '??').substring(0, 2).toUpperCase(); // Deterministic background gradient based on username string let hash = 0; for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } const hue = Math.abs(hash % 360); const background = `linear-gradient(135deg, hsl(${hue}, 65%, 45%) 0%, hsl(${(hue + 40) % 360}, 75%, 30%) 100%)`; return (
{initials}
); } // Custom Avatar Component that fetches from Twitch API Proxy, falls back to initials function ModeratorAvatar({ name, size = 40, border = '1px solid var(--color-border)', API_BASE }) { const [imgSrc, setImgSrc] = React.useState(''); const [hasError, setHasError] = React.useState(false); const [loading, setLoading] = React.useState(true); React.useEffect(() => { if (!name) return; const base = API_BASE || ''; setImgSrc(`${base}/api/avatar/${name.toLowerCase()}`); setHasError(false); setLoading(true); }, [name, API_BASE]); if (hasError || !name) { return ; } return (
{loading && (
)} {name} setLoading(false)} onError={() => setHasError(true)} style={{ width: '100%', height: '100%', objectFit: 'cover', opacity: loading ? 0 : 1, transition: 'opacity 0.2s ease-in-out' }} />
); } export default function ModeratorTab({ twitchConfigured, auth, handleLogin, TWITCH_CHANNEL, modProfiles, selectedMod, setSelectedMod, API_BASE }) { // Set default selected moderator if not set yet useEffect(() => { if (modProfiles && modProfiles.length > 0 && !selectedMod) { setSelectedMod(modProfiles[0]); } }, [modProfiles, selectedMod, setSelectedMod]); // Calculate composite KPI score const getKpiScore = (mod) => { if (!mod || !mod.scores) return 0; const { speed = 50, activity = 50, watchfulness = 50 } = mod.scores; return Math.round((speed + activity + watchfulness) / 3); }; return (
{twitchConfigured && !auth.loggedIn ? (

Доступ Ограничен

Лог действий модераторов и статистика банов/удалений доступны только стримеру и официальным модераторам канала **winx_prinx**. Пожалуйста, авторизуйтесь через Twitch для проверки прав.

) : twitchConfigured && (auth.user?.role !== 'streamer' && auth.user?.role !== 'moderator' && auth.user?.role !== 'admin') ? (

Недостаточно прав

Вы успешно вошли как {auth.user?.displayName}, но вы не являетесь модератором канала {TWITCH_CHANNEL}.

) : (
{(!modProfiles || modProfiles.length <= 2) && (

Данные накапливаются

Информация о модераторах появляется по мере их активности в выбранном стриме.

)} {/* TOP 3 PODIUM */}
{modProfiles && modProfiles.slice(0, 3).map((mod, idx) => { const trophyColor = idx === 0 ? '#FFD700' : idx === 1 ? '#D1D1D6' : '#EAA96E'; const rankName = idx === 0 ? 'Глава патруля' : idx === 1 ? 'Старший мод' : 'Защитник чата'; const isSelected = selectedMod && selectedMod.moderator === mod.moderator; const glowStyle = { borderColor: isSelected ? 'var(--color-brand)' : 'var(--color-border)', background: isSelected ? 'rgba(145, 70, 255, 0.05)' : 'var(--color-bg-card)', boxShadow: isSelected ? '0 8px 30px rgba(145, 70, 255, 0.15)' : 'var(--shadow-md)', position: 'relative', overflow: 'hidden', borderWidth: '1px', borderStyle: 'solid', borderRadius: 'var(--radius-lg)', padding: '1.75rem', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', cursor: 'pointer' }; return (
setSelectedMod(mod)}> {/* Rank background watermark */}
0{idx + 1}
{/* Header metadata */}

{mod.moderator}

{rankName}
{/* Circular KPI progress */}
{/* Quick stats grid */}
Действий
{mod.total_actions}
Реакция
{mod.reaction_time_avg ? `${mod.reaction_time_avg}с` : '—'}
Муты/Баны
{mod.bans_count + mod.timeouts_count}
); })}
{/* MAIN GRID */}
{/* Left Column: Leaderboard Table */}

Рейтинг модераторов

{(!modProfiles || modProfiles.length === 0) ? (

Модераторы еще не совершали действий в этом периоде

) : (
{modProfiles.map((mod, idx) => { const isSelected = selectedMod && selectedMod.moderator === mod.moderator; const kpi = getKpiScore(mod); return ( setSelectedMod(mod)} style={{ cursor: 'pointer', background: isSelected ? 'rgba(145, 70, 255, 0.06)' : 'transparent', transition: 'background-color 0.2s ease' }} > ); })}
Ранг Модератор Реакция Действий Бан/Мут КПД
#{idx + 1}
{mod.moderator}
{mod.reaction_time_avg ? `${mod.reaction_time_avg}с` : '—'} {mod.total_actions} {mod.bans_count}/{mod.timeouts_count}
{kpi}%
)}
{/* Right Column: Detailed Dossier */}

Личное досье модератора

{!selectedMod ? (

Выберите модератора слева для просмотра досье

) : (
{/* Moderator Card Summary */}

{selectedMod.moderator}

Модератор
{/* Circle KPI indicator */}
КПД
{/* 3-column Detailed Metrics */}
Реакция
{selectedMod.reaction_time_avg ? `${selectedMod.reaction_time_avg}с` : '—'}
Действий
{selectedMod.total_actions}
Муты/Баны
{selectedMod.bans_count + selectedMod.timeouts_count}
{/* Radar chart */}
{/* Actions distribution */}

Распределение действий:

{/* Stacked Horizontal Bar */}
{selectedMod.bans_count > 0 && (
)} {selectedMod.timeouts_count > 0 && (
)} {selectedMod.deletions_count > 0 && (
)} {selectedMod.unbans_count > 0 && (
)}
{/* Legend Grid */}
Баны: {selectedMod.bans_count} ({Math.round((selectedMod.bans_count / selectedMod.total_actions) * 100) || 0}%)
Муты: {selectedMod.timeouts_count} ({Math.round((selectedMod.timeouts_count / selectedMod.total_actions) * 100) || 0}%)
Удаления: {selectedMod.deletions_count} ({Math.round((selectedMod.deletions_count / selectedMod.total_actions) * 100) || 0}%)
Разбаны: {selectedMod.unbans_count} ({Math.round((selectedMod.unbans_count / selectedMod.total_actions) * 100) || 0}%)
)}
)}
); }