Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
155 lines
5.4 KiB
TypeScript
155 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import { use } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { trpc } from '@/lib/trpc/client';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { ArrowLeft } from 'lucide-react';
|
|
import type { Route } from 'next';
|
|
|
|
export default function AwardDetailPage({
|
|
params: paramsPromise
|
|
}: {
|
|
params: Promise<{ competitionId: string; awardId: string }>;
|
|
}) {
|
|
const params = use(paramsPromise);
|
|
const router = useRouter();
|
|
const { data: award, isLoading } = trpc.specialAward.get.useQuery({
|
|
id: params.awardId
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Loading...</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!award) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Award Not Found</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() =>
|
|
router.push(`/admin/competitions/${params.competitionId}/awards` as Route)
|
|
}
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
<div className="flex-1">
|
|
<h1 className="text-3xl font-bold">{award.name}</h1>
|
|
<p className="text-muted-foreground">{award.description || 'No description'}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<Tabs defaultValue="overview" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
<TabsTrigger value="eligible">Eligible Projects</TabsTrigger>
|
|
<TabsTrigger value="winners">Winners</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="overview" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Award Information</CardTitle>
|
|
<CardDescription>Configuration and settings</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<p className="text-sm font-medium text-muted-foreground">Scoring Mode</p>
|
|
<Badge variant="outline" className="mt-1">
|
|
{award.scoringMode}
|
|
</Badge>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-muted-foreground">AI Eligibility</p>
|
|
<Badge variant="outline" className="mt-1">
|
|
{award.useAiEligibility ? 'Enabled' : 'Disabled'}
|
|
</Badge>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-muted-foreground">Status</p>
|
|
<Badge variant={award.status === 'DRAFT' ? 'secondary' : 'default'} className="mt-1">
|
|
{award.status}
|
|
</Badge>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-muted-foreground">Program</p>
|
|
<p className="mt-1 text-sm">{award.program?.name}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="eligible" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Eligible Projects</CardTitle>
|
|
<CardDescription>
|
|
Projects that qualify for this award ({award?.eligibleCount || 0})
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-center text-muted-foreground">
|
|
{award?.eligibleCount || 0} eligible projects
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="winners" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Award Winners</CardTitle>
|
|
<CardDescription>Selected winners for this award</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{award?.winnerProject ? (
|
|
<div className="rounded-lg border p-4">
|
|
<div>
|
|
<p className="font-medium">{award.winnerProject.title}</p>
|
|
<p className="text-sm text-muted-foreground">{award.winnerProject.teamName}</p>
|
|
</div>
|
|
<Badge className="mt-2">Winner</Badge>
|
|
</div>
|
|
) : (
|
|
<p className="text-center text-muted-foreground">No winner selected yet</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|