import React, { useMemo, useState } from 'react'; import { Medal, CheckCircle2, History, LineChart as LineChartIcon, List } from 'lucide-react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { useCTF } from './CTFContext'; import { calculateChallengeValue, getFirstBloodBonusFactor, calculateTeamTotalScore } from './services/scoring'; const COLORS = [ '#ff0000', // Red '#00ff00', // Green '#0000ff', // Blue '#ffff00', // Yellow '#ff00ff', // Magenta '#00ffff', // Cyan '#ffaa00', // Orange '#bf00ff', // Purple '#ff0080', // Pink '#aaff00' // Lime ]; export const Log: React.FC = () => { const { state } = useCTF(); const [view, setView] = useState<'log' | 'graph'>('log'); const sortedSolves = useMemo(() => { return [...state.solves].sort((a, b) => b.timestamp - a.timestamp); }, [state.solves]); const topTeams = useMemo(() => { return state.teams .filter(t => !t.isAdmin && !t.isDisabled) .map(team => ({ ...team, score: calculateTeamTotalScore(team.id, state.challenges, state.solves) })) .sort((a, b) => b.score - a.score) .slice(0, 3); }, [state]); const top10Teams = useMemo(() => { return state.teams .filter(t => !t.isAdmin && !t.isDisabled) .map(team => ({ ...team, score: calculateTeamTotalScore(team.id, state.challenges, state.solves) })) .sort((a, b) => b.score - a.score) .slice(0, 10); }, [state]); const graphData = useMemo(() => { if (state.solves.length === 0) return []; const sortedSolvesAsc = [...state.solves].sort((a, b) => a.timestamp - b.timestamp); const dataPoints: any[] = []; const startTime = state.startTime || (sortedSolvesAsc.length > 0 ? sortedSolvesAsc[0].timestamp - 1000 : 0); const initialPoint: any = { time: startTime, displayTime: new Date(startTime).toLocaleTimeString() }; top10Teams.forEach(t => initialPoint[t.name] = 0); dataPoints.push(initialPoint); sortedSolvesAsc.forEach((solve, idx) => { const solvesUpToNow = sortedSolvesAsc.slice(0, idx + 1); const point: any = { time: solve.timestamp, displayTime: new Date(solve.timestamp).toLocaleTimeString() }; top10Teams.forEach(team => { let total = 0; const teamSolves = solvesUpToNow.filter(s => s.teamId === team.id); teamSolves.forEach(ts => { const challenge = state.challenges.find(c => c.id === ts.challengeId); if (!challenge) return; const challengeSolvesUpToNow = solvesUpToNow.filter(s => s.challengeId === ts.challengeId); const baseValue = calculateChallengeValue( challenge.initialPoints, challenge.minimumPoints || 0, challenge.decaySolves || 1, challengeSolvesUpToNow.length ); const rank = challengeSolvesUpToNow.findIndex(s => s.teamId === team.id); const bonus = Math.floor(challenge.initialPoints * getFirstBloodBonusFactor(rank)); total += (baseValue + bonus); }); point[team.name] = total; }); dataPoints.push(point); }); return dataPoints; }, [state, top10Teams]); return (

Event Log

{topTeams.length > 0 && (
{topTeams.map((team, idx) => (
{idx + 1} {team.name} {team.score} PTS
))}
)}
{view === 'log' ? (
{sortedSolves.length === 0 ? (
No activities recorded yet.
) : ( sortedSolves.map((solve, idx) => { const team = state.teams.find(t => t.id === solve.teamId); const challenge = state.challenges.find(c => c.id === solve.challengeId); if (!team || !challenge) return null; const challengeSolves = state.solves .filter(s => s.challengeId === solve.challengeId) .sort((a, b) => a.timestamp - b.timestamp); const rank = challengeSolves.findIndex(s => s.teamId === solve.teamId); const bonusFactor = getFirstBloodBonusFactor(rank); const basePoints = calculateChallengeValue( challenge.initialPoints, challenge.minimumPoints || 0, challenge.decaySolves || 1, challengeSolves.length ); const bonus = Math.floor(challenge.initialPoints * bonusFactor); const pointsGained = basePoints + bonus; const getRankIcon = (rank: number) => { if (rank === 0) return ; // Gold if (rank === 1) return ; // Silver if (rank === 2) return ; // Bronze return ; // Checkmark }; const difficultyColor = challenge.difficulty === 'Low' ? 'text-[#00ff00]' : challenge.difficulty === 'Medium' ? 'text-[#ffaa00]' : 'text-[#ff0000]'; const dateStr = new Date(solve.timestamp).toLocaleString(); return (
{getRankIcon(rank)}
[{dateStr}] {team.name} solved {challenge.category} - {challenge.title} and gained {pointsGained} points.
); }) )}
) : (

Top 10 Teams - Score Progression

{graphData.length === 0 ? (
No data to display.
) : ( {top10Teams.map((team, idx) => ( ))} )}
)}
); };