'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 { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Button } from '@/components/ui/button' import { StatusBadge } from '@/components/shared/status-badge' import { FolderKanban, ClipboardList, Users, CheckCircle2, Eye, BarChart3, Search, ChevronLeft, ChevronRight, } from 'lucide-react' import { cn } from '@/lib/utils' import { useDebouncedCallback } from 'use-debounce' const PER_PAGE_OPTIONS = [10, 20, 50] export function ObserverDashboardContent({ userName }: { userName?: string }) { const [selectedRoundId, setSelectedRoundId] = useState('all') const [search, setSearch] = useState('') const [debouncedSearch, setDebouncedSearch] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [page, setPage] = useState(1) const [perPage, setPerPage] = useState(20) const debouncedSetSearch = useDebouncedCallback((value: string) => { setDebouncedSearch(value) setPage(1) }, 300) const handleSearchChange = (value: string) => { setSearch(value) debouncedSetSearch(value) } const handleRoundChange = (value: string) => { setSelectedRoundId(value) setPage(1) } const handleStatusChange = (value: string) => { setStatusFilter(value) setPage(1) } // Fetch programs/rounds for the filter dropdown const { data: programs } = 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`, status: r.status, })) ) || [] // Fetch dashboard stats const roundIdParam = selectedRoundId !== 'all' ? selectedRoundId : undefined const { data: stats, isLoading: statsLoading } = trpc.analytics.getDashboardStats.useQuery( { roundId: roundIdParam } ) // Fetch projects const { data: projectsData, isLoading: projectsLoading } = trpc.analytics.getAllProjects.useQuery({ roundId: roundIdParam, search: debouncedSearch || undefined, status: statusFilter !== 'all' ? statusFilter : undefined, page, perPage, }) // Fetch recent rounds for jury completion const { data: recentRoundsData } = trpc.program.list.useQuery({ includeRounds: true }) const recentRounds = recentRoundsData?.flatMap((p) => p.rounds.map((r) => ({ ...r, programName: `${p.year} Edition`, })) )?.slice(0, 5) || [] return (
{/* Header */}

Dashboard

Welcome, {userName || 'Observer'}

{/* Observer Notice */}

Observer Mode

Read-Only

You have read-only access to view platform statistics and reports.

{/* Round Filter */}
{/* Stats Grid */} {statsLoading ? (
{[...Array(4)].map((_, i) => ( ))}
) : stats ? (
Programs
{stats.programCount}

{stats.activeRoundCount} active round{stats.activeRoundCount !== 1 ? 's' : ''}

Projects
{stats.projectCount}

{selectedRoundId !== 'all' ? 'In selected round' : 'Across all rounds'}

Jury Members
{stats.jurorCount}

Active members

Evaluations
{stats.submittedEvaluations}

{stats.completionRate}% completion rate

) : null} {/* Projects Table */} All Projects {projectsData ? `${projectsData.total} project${projectsData.total !== 1 ? 's' : ''} found` : 'Loading projects...'} {/* Search & Filter Bar */}
handleSearchChange(e.target.value)} className="pl-10" />
{projectsLoading ? (
{[...Array(5)].map((_, i) => ( ))}
) : projectsData && projectsData.projects.length > 0 ? ( <> {/* Desktop Table */}
Title Team Round Status Avg Score Evaluations {projectsData.projects.map((project) => ( {project.title} {project.teamName || '-'} {project.roundName} {project.averageScore !== null ? project.averageScore.toFixed(2) : '-'} {project.evaluationCount} ))}
{/* Mobile Cards */}
{projectsData.projects.map((project) => (

{project.title}

{project.teamName && (

{project.teamName}

)}
{project.roundName}
Score: {project.averageScore !== null ? project.averageScore.toFixed(2) : '-'} {project.evaluationCount} eval{project.evaluationCount !== 1 ? 's' : ''}
))}
{/* Pagination */} {projectsData.totalPages > 1 && (

Page {projectsData.page} of {projectsData.totalPages}

)} ) : (

{debouncedSearch || statusFilter !== 'all' ? 'No projects match your filters' : 'No projects found'}

)}
{/* Score Distribution */} {stats && stats.scoreDistribution.some((b) => b.count > 0) && ( Score Distribution Distribution of global scores across evaluations
{(() => { const maxCount = Math.max(...stats.scoreDistribution.map((b) => b.count), 1) const colors = ['bg-green-500', 'bg-emerald-400', 'bg-amber-400', 'bg-orange-400', 'bg-red-400'] return stats.scoreDistribution.map((bucket, i) => (
{bucket.label}
0 ? (bucket.count / maxCount) * 100 : 0}%` }} />
{bucket.count}
)) })()}
)} {/* Recent Rounds */} {recentRounds.length > 0 && ( Recent Rounds Overview of the latest voting rounds
{recentRounds.map((round) => (

{round.name}

{round.status}

{round.programName}

{round._count?.projects || 0} projects

{round._count?.assignments || 0} assignments

))}
)}
) }