'use client'
import { useState } from 'react'
import { trpc } from '@/lib/trpc/client'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
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,
BarChart3,
Users,
ClipboardList,
CheckCircle2,
TrendingUp,
GitCompare,
UserCheck,
Globe,
Printer,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { formatDateOnly } from '@/lib/utils'
import {
ScoreDistributionChart,
EvaluationTimelineChart,
StatusBreakdownChart,
JurorWorkloadChart,
ProjectRankingsChart,
CriteriaScoresChart,
CrossRoundComparisonChart,
JurorConsistencyChart,
DiversityMetricsChart,
} from '@/components/charts'
function OverviewTab({ selectedRoundId }: { selectedRoundId: string | null }) {
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeRounds: true })
const rounds = programs?.flatMap(p =>
p.rounds.map(r => ({
...r,
programName: `${p.year} Edition`,
}))
) || []
const { data: overviewStats, isLoading: statsLoading } =
trpc.analytics.getOverviewStats.useQuery(
{ roundId: selectedRoundId! },
{ enabled: !!selectedRoundId }
)
if (isLoading) {
return (
{[...Array(4)].map((_, i) => (
))}
)
}
const totalProjects = rounds.reduce((acc, r) => acc + (r._count?.projects || 0), 0)
const activeRounds = rounds.filter((r) => r.status === 'ACTIVE').length
const totalPrograms = programs?.length || 0
return (
{/* Quick Stats */}
Total Rounds
{rounds.length}
{activeRounds} active
Total Projects
{totalProjects}
Across all rounds
Active Rounds
{activeRounds}
Currently active
Programs
{totalPrograms}
Total programs
{/* Round-specific overview stats */}
{selectedRoundId && (
<>
{statsLoading ? (
{[...Array(4)].map((_, i) => (
))}
) : overviewStats ? (
Selected Round Details
Projects
{overviewStats.projectCount}
In this round
Assignments
{overviewStats.assignmentCount}
{overviewStats.jurorCount} jurors
Evaluations
{overviewStats.evaluationCount}
Submitted
Completion
{overviewStats.completionRate}%
) : null}
>
)}
{/* Rounds Table - Desktop */}
Round Reports
Progress overview for each round
Round
Program
Projects
Status
{rounds.map((round) => (
{round.name}
{round.votingEndAt && (
Ends: {formatDateOnly(round.votingEndAt)}
)}
{round.programName}
{round._count?.projects || '-'}
{round.status}
))}
{/* Rounds Cards - Mobile */}
Round Reports
{rounds.map((round) => (
{round.name}
{round.status}
{round.programName}
{round.votingEndAt && (
Ends: {formatDateOnly(round.votingEndAt)}
)}
{round._count?.projects || 0} projects
))}
)
}
function AnalyticsTab({ selectedRoundId }: { selectedRoundId: string }) {
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 }
)
return (
{/* 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
)}
)
}
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 (
Select Rounds to Compare
Choose at least 2 rounds
{rounds.map((round) => (
toggleRound(round.id)}
>
{round.programName} - {round.name}
))}
{selectedRoundIds.length < 2 && (
Select at least 2 rounds to enable comparison
)}
{comparisonLoading && selectedRoundIds.length >= 2 &&
}
{comparison && (
} />
)}
)
}
function JurorConsistencyTab({ selectedRoundId }: { selectedRoundId: string }) {
const { data: consistency, isLoading } =
trpc.analytics.getJurorConsistency.useQuery(
{ roundId: selectedRoundId },
{ enabled: !!selectedRoundId }
)
if (isLoading) return
if (!consistency) return null
return (
}}
/>
)
}
function DiversityTab({ selectedRoundId }: { selectedRoundId: string }) {
const { data: diversity, isLoading } =
trpc.analytics.getDiversityMetrics.useQuery(
{ roundId: selectedRoundId },
{ enabled: !!selectedRoundId }
)
if (isLoading) return
if (!diversity) return null
return (
)
}
export default function ObserverReportsPage() {
const [selectedRoundId, setSelectedRoundId] = useState(null)
const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeRounds: true })
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)
}
return (
{/* Header */}
Reports
View evaluation progress and statistics
{/* Round Selector */}
{roundsLoading ? (
) : rounds.length > 0 ? (
) : (
No rounds available
)}
{/* Tabs */}
Overview
Analytics
Cross-Round
Juror Consistency
Diversity
{selectedRoundId ? (
) : (
Select a round
Choose a round from the dropdown above to view analytics
)}
{selectedRoundId ? (
) : (
Select a round
Choose a round above to view juror consistency metrics
)}
{selectedRoundId ? (
) : (
Select a round
Choose a round above to view diversity metrics
)}
)
}