'use client' import { useState } from 'react' import Link from 'next/link' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Progress } from '@/components/ui/progress' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { FileSpreadsheet, Download, BarChart3, Users, ClipboardList, CheckCircle2, PieChart, TrendingUp, GitCompare, UserCheck, Globe, Printer, } from 'lucide-react' import { formatDateOnly } from '@/lib/utils' import { ScoreDistributionChart, EvaluationTimelineChart, StatusBreakdownChart, JurorWorkloadChart, ProjectRankingsChart, CriteriaScoresChart, GeographicDistribution, CrossRoundComparisonChart, JurorConsistencyChart, DiversityMetricsChart, } from '@/components/charts' function ReportsOverview() { const { data: programs, isLoading } = trpc.program.list.useQuery({ includeRounds: true }) // Flatten rounds from all programs const rounds = programs?.flatMap(p => p.rounds.map(r => ({ ...r, programId: p.id, programName: `${p.year} Edition` }))) || [] if (isLoading) { return (
{[...Array(4)].map((_, i) => ( ))}
) } if (!rounds || rounds.length === 0) { return (

No data to report

Create rounds and assign jury members to generate reports

) } // Calculate totals const totalProjects = programs?.reduce((acc, p) => acc + (p._count?.rounds || 0), 0) || 0 const totalPrograms = programs?.length || 0 const activeRounds = rounds.filter((r) => r.status === 'ACTIVE').length return (
{/* Quick Stats */}
Total Rounds
{rounds.length}

{activeRounds} active

Total Projects
{totalProjects}

Across all rounds

Active Rounds
{activeRounds}

Currently active

Programs
{totalPrograms}

Total programs

{/* Rounds Table */} Round Reports View progress and export data for each round Round Program Projects Status Export {rounds.map((round) => (

{round.name}

{round.votingEndAt && (

Ends: {formatDateOnly(round.votingEndAt)}

)}
{round.programName} - {round.status}
))}
) } function RoundAnalytics() { const [selectedRoundId, setSelectedRoundId] = useState(null) const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeRounds: true }) // Flatten rounds from all programs with program name const rounds = programs?.flatMap(p => p.rounds.map(r => ({ ...r, programName: `${p.year} Edition` }))) || [] // Set default selected round if (rounds.length && !selectedRoundId) { setSelectedRoundId(rounds[0].id) } const { data: scoreDistribution, isLoading: scoreLoading } = trpc.analytics.getScoreDistribution.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) const { data: timeline, isLoading: timelineLoading } = trpc.analytics.getEvaluationTimeline.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) const { data: statusBreakdown, isLoading: statusLoading } = trpc.analytics.getStatusBreakdown.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) const { data: jurorWorkload, isLoading: workloadLoading } = trpc.analytics.getJurorWorkload.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) const { data: projectRankings, isLoading: rankingsLoading } = trpc.analytics.getProjectRankings.useQuery( { roundId: selectedRoundId!, limit: 15 }, { enabled: !!selectedRoundId } ) const { data: criteriaScores, isLoading: criteriaLoading } = trpc.analytics.getCriteriaScores.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) const selectedRound = rounds.find((r) => r.id === selectedRoundId) const { data: geoData, isLoading: geoLoading } = trpc.analytics.getGeographicDistribution.useQuery( { programId: selectedRound?.programId || '', roundId: selectedRoundId! }, { enabled: !!selectedRoundId && !!selectedRound?.programId } ) if (roundsLoading) { return (
) } if (!rounds?.length) { return (

No rounds available

Create a round to view analytics

) } return (
{/* Round Selector */}
{selectedRoundId && (
{/* Row 1: Score Distribution & Status Breakdown */}
{scoreLoading ? ( ) : scoreDistribution ? ( ) : null} {statusLoading ? ( ) : statusBreakdown ? ( ) : null}
{/* Row 2: Evaluation Timeline */} {timelineLoading ? ( ) : timeline?.length ? ( ) : (

No evaluation data available yet

)} {/* Row 3: Criteria Scores */} {criteriaLoading ? ( ) : criteriaScores?.length ? ( ) : null} {/* Row 4: Juror Workload */} {workloadLoading ? ( ) : jurorWorkload?.length ? ( ) : (

No juror assignments yet

)} {/* Row 5: Project Rankings */} {rankingsLoading ? ( ) : projectRankings?.length ? ( ) : (

No project scores available yet

)} {/* Row 6: Geographic Distribution */} {geoLoading ? ( ) : geoData?.length ? ( ) : null}
)}
) } function CrossRoundTab() { const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true }) const rounds = programs?.flatMap(p => p.rounds.map(r => ({ id: r.id, name: r.name, programName: `${p.year} Edition` })) ) || [] const [selectedRoundIds, setSelectedRoundIds] = useState([]) const { data: comparison, isLoading: comparisonLoading } = trpc.analytics.getCrossRoundComparison.useQuery( { roundIds: selectedRoundIds }, { enabled: selectedRoundIds.length >= 2 } ) const toggleRound = (roundId: string) => { setSelectedRoundIds((prev) => prev.includes(roundId) ? prev.filter((id) => id !== roundId) : [...prev, roundId] ) } if (programsLoading) { return } return (
{/* Round selector */} Select Rounds to Compare Choose at least 2 rounds to compare metrics side by side
{rounds.map((round) => { const isSelected = selectedRoundIds.includes(round.id) return ( toggleRound(round.id)} > {round.programName} - {round.name} ) })}
{selectedRoundIds.length < 2 && (

Select at least 2 rounds to enable comparison

)}
{/* Comparison charts */} {comparisonLoading && selectedRoundIds.length >= 2 && (
)} {comparison && ( } /> )}
) } function JurorConsistencyTab() { const [selectedRoundId, setSelectedRoundId] = useState(null) const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true }) const rounds = programs?.flatMap(p => p.rounds.map(r => ({ id: r.id, name: r.name, programName: `${p.year} Edition` })) ) || [] if (rounds.length && !selectedRoundId) { setSelectedRoundId(rounds[0].id) } const { data: consistency, isLoading: consistencyLoading } = trpc.analytics.getJurorConsistency.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) if (programsLoading) { return } return (
{consistencyLoading && } {consistency && ( }} /> )}
) } function DiversityTab() { const [selectedRoundId, setSelectedRoundId] = useState(null) const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true }) const rounds = programs?.flatMap(p => p.rounds.map(r => ({ id: r.id, name: r.name, programName: `${p.year} Edition` })) ) || [] if (rounds.length && !selectedRoundId) { setSelectedRoundId(rounds[0].id) } const { data: diversity, isLoading: diversityLoading } = trpc.analytics.getDiversityMetrics.useQuery( { roundId: selectedRoundId! }, { enabled: !!selectedRoundId } ) if (programsLoading) { return } return (
{diversityLoading && } {diversity && ( )}
) } export default function ReportsPage() { return (
{/* Header */}

Reports

View progress, analytics, and export evaluation data

{/* Tabs */}
Overview Analytics Cross-Round Juror Consistency Diversity
) }