refactor(final-docs): shared review component reachable by jury + admin routes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-06-09 15:47:56 +02:00
parent df0be6bb48
commit b66e2071f9
4 changed files with 158 additions and 138 deletions

View File

@@ -0,0 +1,7 @@
import { FinalsDocumentsReview } from '@/components/finals/finals-documents-review'
export const dynamic = 'force-dynamic'
export default function AdminFinalsDocumentsPage() {
return <FinalsDocumentsReview />
}

View File

@@ -1532,7 +1532,7 @@ export default function RoundDetailPage() {
<FinalistEnrollmentCard programId={programId} roundId={roundId} />
<div className="flex justify-end gap-2">
<Button asChild variant="outline" size="sm">
<Link href="/jury/finals-documents">
<Link href="/admin/finals-documents">
<FileText className="mr-2 h-4 w-4" />
Review finalist documents
</Link>

View File

@@ -1,141 +1,7 @@
'use client'
import { FinalsDocumentsReview } from '@/components/finals/finals-documents-review'
import { trpc } from '@/lib/trpc/client'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { FilePreview } from '@/components/shared/file-viewer'
import { FileText, Download, ShieldAlert } from 'lucide-react'
export const dynamic = 'force-dynamic'
export default function FinalsDocumentsPage() {
const { data: programId, isLoading: programLoading } =
trpc.competition.getActiveProgramId.useQuery()
const { data, isLoading, error } = trpc.finalist.listReviewDocuments.useQuery(
{ programId: programId! },
{ enabled: !!programId, retry: false },
)
if (error?.data?.code === 'FORBIDDEN') {
return (
<Card>
<CardContent className="flex flex-col items-center py-12 text-center">
<ShieldAlert className="h-10 w-10 text-muted-foreground/50 mb-3" />
<p className="font-medium">No access</p>
<p className="text-sm text-muted-foreground">
This review is for the Grand-Final jury and program admins.
</p>
</CardContent>
</Card>
)
}
// No active program resolved — nothing to review.
if (!programLoading && !programId) {
return (
<Card>
<CardContent className="flex flex-col items-center py-12 text-center">
<FileText className="h-10 w-10 text-muted-foreground/50 mb-3" />
<p className="font-medium">No active program</p>
<p className="text-sm text-muted-foreground">
Finalist documents will appear here once a program is active.
</p>
</CardContent>
</Card>
)
}
if (isLoading || !data) {
return (
<div className="space-y-4">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-64" />
</div>
)
}
const fmt = new Intl.DateTimeFormat(undefined, {
dateStyle: 'long',
timeStyle: 'short',
})
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight text-brand-blue">
Finalist Documents
</h1>
<p className="text-muted-foreground mt-1">
{data.submittedCount} of {data.totalCount} teams complete
{data.round.deadline
? ` · due ${fmt.format(new Date(data.round.deadline))}`
: ''}
</p>
</div>
{data.teams.map((team) => (
<Card key={team.projectId}>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-lg">{team.teamName}</CardTitle>
<div className="flex items-center gap-2">
{team.category && (
<Badge variant="secondary">{team.category}</Badge>
)}
<Badge
variant={team.submitted ? 'default' : 'outline'}
className={
team.submitted
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
: ''
}
>
{team.submitted ? 'Complete' : 'Incomplete'}
</Badge>
</div>
</CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2">
{team.documents.map((doc) => (
<div
key={doc.requirementId}
className="rounded-lg border p-3 space-y-2"
>
<div className="flex items-center justify-between">
<span className="font-medium text-sm flex items-center gap-2">
<FileText className="h-4 w-4" /> {doc.requirementName}
</span>
{doc.file && (
<Button
asChild
variant="ghost"
size="sm"
className="h-7 px-2 text-xs"
>
<a
href={doc.file.url}
target="_blank"
rel="noreferrer"
>
<Download className="h-3 w-3 mr-1" /> Open
</a>
</Button>
)}
</div>
{doc.file ? (
<FilePreview
file={{
mimeType: doc.file.mimeType,
fileName: doc.file.fileName,
}}
url={doc.file.url}
/>
) : (
<p className="text-sm text-muted-foreground py-4 text-center">
Not yet uploaded
</p>
)}
</div>
))}
</CardContent>
</Card>
))}
</div>
)
return <FinalsDocumentsReview />
}