'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,
} from 'lucide-react'
import { formatDateOnly } from '@/lib/utils'
import {
ScoreDistributionChart,
EvaluationTimelineChart,
StatusBreakdownChart,
JurorWorkloadChart,
ProjectRankingsChart,
CriteriaScoresChart,
CrossStageComparisonChart,
JurorConsistencyChart,
DiversityMetricsChart,
} from '@/components/charts'
import { ExportPdfButton } from '@/components/shared/export-pdf-button'
import { AnimatedCard } from '@/components/shared/animated-container'
// Parse selection value: "all:programId" for edition-wide, or stageId
function parseSelection(value: string | null): { stageId?: string; programId?: string } {
if (!value) return {}
if (value.startsWith('all:')) return { programId: value.slice(4) }
return { stageId: value }
}
function OverviewTab({ selectedValue }: { selectedValue: string | null }) {
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
const stages = programs?.flatMap(p =>
(p.stages as { id: string; name: string; status: string; windowCloseAt: Date | null; _count: { projects: number; assignments: number } }[]).map(s => ({
...s,
programName: `${p.year} Edition`,
}))
) || []
const queryInput = parseSelection(selectedValue)
const hasSelection = !!queryInput.stageId || !!queryInput.programId
const { data: overviewStats, isLoading: statsLoading } =
trpc.analytics.getOverviewStats.useQuery(
queryInput,
{ enabled: hasSelection }
)
if (isLoading) {
return (
{[...Array(4)].map((_, i) => (
))}
)
}
const totalProjects = stages.reduce((acc, s) => acc + (s._count?.projects || 0), 0)
const activeStages = stages.filter((s) => s.status === 'STAGE_ACTIVE').length
const totalPrograms = programs?.length || 0
return (
{/* Quick Stats */}
Total Stages
{stages.length}
{activeStages} active
Total Projects
{totalProjects}
Across all stages
Active Stages
{activeStages}
Currently active
Programs
{totalPrograms}
Total programs
{/* Round/edition-specific overview stats */}
{hasSelection && (
<>
{statsLoading ? (
{[...Array(4)].map((_, i) => (
))}
) : overviewStats ? (
{queryInput.programId ? 'Edition Overview' : 'Selected Round Details'}
Projects
{overviewStats.projectCount}
In this round
Assignments
{overviewStats.assignmentCount}
{overviewStats.jurorCount} jurors
Evaluations
{overviewStats.evaluationCount}
Submitted
Completion
{overviewStats.completionRate}%
) : null}
>
)}
{/* Stages Table - Desktop */}
Stage Reports
Progress overview for each stage
Stage
Program
Projects
Status
{stages.map((stage) => (
{stage.name}
{stage.windowCloseAt && (
Ends: {formatDateOnly(stage.windowCloseAt)}
)}
{stage.programName}
{stage._count?.projects || '-'}
{stage.status === 'STAGE_ACTIVE' ? 'Active' : stage.status === 'STAGE_CLOSED' ? 'Closed' : stage.status}
))}
{/* Stages Cards - Mobile */}
Stage Reports
{stages.map((stage) => (
{stage.name}
{stage.status === 'STAGE_ACTIVE' ? 'Active' : stage.status === 'STAGE_CLOSED' ? 'Closed' : stage.status}
{stage.programName}
{stage.windowCloseAt && (
Ends: {formatDateOnly(stage.windowCloseAt)}
)}
{stage._count?.projects || 0} projects
))}
)
}
function AnalyticsTab({ selectedValue }: { selectedValue: string }) {
const queryInput = parseSelection(selectedValue)
const hasSelection = !!queryInput.stageId || !!queryInput.programId
const { data: scoreDistribution, isLoading: scoreLoading } =
trpc.analytics.getScoreDistribution.useQuery(
queryInput,
{ enabled: hasSelection }
)
const { data: timeline, isLoading: timelineLoading } =
trpc.analytics.getEvaluationTimeline.useQuery(
queryInput,
{ enabled: hasSelection }
)
const { data: statusBreakdown, isLoading: statusLoading } =
trpc.analytics.getStatusBreakdown.useQuery(
queryInput,
{ enabled: hasSelection }
)
const { data: jurorWorkload, isLoading: workloadLoading } =
trpc.analytics.getJurorWorkload.useQuery(
queryInput,
{ enabled: hasSelection }
)
const { data: projectRankings, isLoading: rankingsLoading } =
trpc.analytics.getProjectRankings.useQuery(
{ ...queryInput, limit: 15 },
{ enabled: hasSelection }
)
const { data: criteriaScores, isLoading: criteriaLoading } =
trpc.analytics.getCriteriaScores.useQuery(
queryInput,
{ enabled: hasSelection }
)
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 CrossStageTab() {
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
const stages = programs?.flatMap(p =>
((p.stages || []) as Array<{ id: string; name: string }>).map(s => ({ id: s.id, name: s.name, programName: `${p.year} Edition` }))
) || []
const [selectedStageIds, setSelectedStageIds] = useState([])
const { data: comparison, isLoading: comparisonLoading } =
trpc.analytics.getCrossStageComparison.useQuery(
{ stageIds: selectedStageIds },
{ enabled: selectedStageIds.length >= 2 }
)
const toggleStage = (stageId: string) => {
setSelectedStageIds((prev) =>
prev.includes(stageId)
? prev.filter((id) => id !== stageId)
: [...prev, stageId]
)
}
if (programsLoading) return
return (
Select Stages to Compare
Choose at least 2 stages
{stages.map((stage) => (
toggleStage(stage.id)}
>
{stage.programName} - {stage.name}
))}
{selectedStageIds.length < 2 && (
Select at least 2 stages to enable comparison
)}
{comparisonLoading && selectedStageIds.length >= 2 &&
}
{comparison && (
} />
)}
)
}
function JurorConsistencyTab({ selectedValue }: { selectedValue: string }) {
const queryInput = parseSelection(selectedValue)
const hasSelection = !!queryInput.stageId || !!queryInput.programId
const { data: consistency, isLoading } =
trpc.analytics.getJurorConsistency.useQuery(
queryInput,
{ enabled: hasSelection }
)
if (isLoading) return
if (!consistency) return null
return (
}}
/>
)
}
function DiversityTab({ selectedValue }: { selectedValue: string }) {
const queryInput = parseSelection(selectedValue)
const hasSelection = !!queryInput.stageId || !!queryInput.programId
const { data: diversity, isLoading } =
trpc.analytics.getDiversityMetrics.useQuery(
queryInput,
{ enabled: hasSelection }
)
if (isLoading) return
if (!diversity) return null
return (
)
}
export default function ObserverReportsPage() {
const [selectedValue, setSelectedValue] = useState(null)
const { data: programs, isLoading: stagesLoading } = trpc.program.list.useQuery({ includeStages: true })
const stages = programs?.flatMap(p =>
(p.stages as { id: string; name: string; status: string; windowCloseAt: Date | null; _count: { projects: number; assignments: number } }[]).map(s => ({
...s,
programId: p.id,
programName: `${p.year} Edition`,
}))
) || []
// Set default selected stage
if (stages.length && !selectedValue) {
setSelectedValue(stages[0].id)
}
const hasSelection = !!selectedValue
const selectedStage = stages.find((s) => s.id === selectedValue)
return (
{/* Header */}
Reports
View evaluation progress and statistics
{/* Stage Selector */}
{stagesLoading ? (
) : stages.length > 0 ? (
) : (
No stages available
)}
{/* Tabs */}
Overview
Analytics
Cross-Round
Juror Consistency
Diversity
{selectedValue && !selectedValue.startsWith('all:') && (
)}
{hasSelection ? (
) : (
Select a round
Choose a round or edition from the dropdown above to view analytics
)}
{hasSelection ? (
) : (
Select a round
Choose a round or edition above to view juror consistency metrics
)}
{hasSelection ? (
) : (
Select a round
Choose a round or edition above to view diversity metrics
)}
)
}