'use client' import { useState, useCallback } from 'react' import Link from 'next/link' import type { Route } from 'next' import { useRouter } from 'next/navigation' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' 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 { StatusBadge } from '@/components/shared/status-badge' import { CsvExportDialog } from '@/components/shared/csv-export-dialog' import { scoreGradient } from '@/components/charts/chart-theme' import { Search, ChevronLeft, ChevronRight, ArrowUpDown, ArrowUp, ArrowDown, ClipboardList, Download, X, } from 'lucide-react' import { cn } from '@/lib/utils' import { useDebouncedCallback } from 'use-debounce' export function ObserverProjectsContent() { const router = useRouter() const [search, setSearch] = useState('') const [debouncedSearch, setDebouncedSearch] = useState('') const [roundFilter, setRoundFilter] = useState('all') const [statusFilter, setStatusFilter] = useState('all') const [sortBy, setSortBy] = useState<'title' | 'score' | 'evaluations'>('title') const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc') const [page, setPage] = useState(1) const [perPage] = useState(20) const [csvOpen, setCsvOpen] = useState(false) const [csvExportData, setCsvExportData] = useState< { data: Record[]; columns: string[] } | undefined >(undefined) const [csvLoading, setCsvLoading] = useState(false) const debouncedSetSearch = useDebouncedCallback((value: string) => { setDebouncedSearch(value) setPage(1) }, 300) const handleSearchChange = (value: string) => { setSearch(value) debouncedSetSearch(value) } const handleRoundChange = (value: string) => { setRoundFilter(value) setPage(1) } const handleStatusChange = (value: string) => { setStatusFilter(value) setPage(1) } const handleSort = (column: 'title' | 'score' | 'evaluations') => { if (sortBy === column) { setSortDir(sortDir === 'asc' ? 'desc' : 'asc') } else { setSortBy(column) setSortDir(column === 'title' ? 'asc' : 'desc') } setPage(1) } const clearFilters = () => { setSearch('') setDebouncedSearch('') setRoundFilter('all') setStatusFilter('all') setPage(1) } const activeFilterCount = (debouncedSearch ? 1 : 0) + (roundFilter !== 'all' ? 1 : 0) + (statusFilter !== 'all' ? 1 : 0) const { data: programs } = trpc.program.list.useQuery( { includeStages: true }, { refetchInterval: 30_000 }, ) const rounds = programs?.flatMap((p) => (p.rounds ?? []).map((r: { id: string; name: string; status: string; roundType?: string }) => ({ id: r.id, name: r.name, programName: `${p.year} Edition`, status: r.status, roundType: r.roundType, })), ) ?? [] const roundIdParam = roundFilter !== 'all' ? roundFilter : undefined const { data: projectsData, isLoading: projectsLoading } = trpc.analytics.getAllProjects.useQuery( { roundId: roundIdParam, search: debouncedSearch || undefined, status: statusFilter !== 'all' ? statusFilter : undefined, sortBy, sortDir, page, perPage, }, { refetchInterval: 30_000 }, ) const handleRequestCsvData = useCallback(async () => { setCsvLoading(true) try { const allData = await new Promise((resolve) => { resolve(projectsData) }) if (!allData?.projects) { setCsvLoading(false) return undefined } const rows = allData.projects.map((p) => ({ title: p.title, teamName: p.teamName ?? '', country: p.country ?? '', roundName: p.roundName ?? '', status: p.status, averageScore: p.averageScore !== null ? p.averageScore.toFixed(2) : '', evaluationCount: p.evaluationCount, })) const result = { data: rows, columns: ['title', 'teamName', 'country', 'roundName', 'status', 'averageScore', 'evaluationCount'], } setCsvExportData(result) setCsvLoading(false) return result } catch { setCsvLoading(false) return undefined } }, [projectsData]) const SortIcon = ({ column }: { column: 'title' | 'score' | 'evaluations' }) => { if (sortBy !== column) return return sortDir === 'asc' ? ( ) : ( ) } return (

All Projects

{projectsData ? `${projectsData.total} project${projectsData.total !== 1 ? 's' : ''} total` : 'Loading projects...'}

Filters {activeFilterCount > 0 && ( {activeFilterCount} active )}
handleSearchChange(e.target.value)} className="pl-10" />
{projectsLoading ? ( {[...Array(8)].map((_, i) => ( ))} ) : projectsData && projectsData.projects.length > 0 ? ( <>
Country Round Status {projectsData.projects.map((project) => ( router.push(`/observer/projects/${project.id}`)} > e.stopPropagation()} > {project.title} {project.teamName && (

{project.teamName}

)}
{project.country ?? '-'} {project.roundName} {project.averageScore !== null ? (
{project.averageScore.toFixed(1)}
) : ( - )} {project.evaluationCount} e.stopPropagation()} > ))}
{projectsData.projects.map((project) => (

{project.title}

{project.teamName && (

{project.teamName}

)}
{project.roundName}
Score:{' '} {project.averageScore !== null ? project.averageScore.toFixed(1) : '-'} {project.evaluationCount} eval {project.evaluationCount !== 1 ? 's' : ''}
))}

Page {projectsData.page} of {projectsData.totalPages} ·{' '} {projectsData.total} result{projectsData.total !== 1 ? 's' : ''}

) : (

{activeFilterCount > 0 ? 'No projects match your filters' : 'No projects found'}

{activeFilterCount > 0 && ( )}
)}
) }