'use client' import { Suspense, use, useState, useEffect } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Skeleton } from '@/components/ui/skeleton' import { Badge } from '@/components/ui/badge' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { EvaluationFormBuilder, type Criterion, } from '@/components/forms/evaluation-form-builder' import { RoundTypeSettings } from '@/components/forms/round-type-settings' import { ArrowLeft, Loader2, AlertCircle, AlertTriangle } from 'lucide-react' import { format } from 'date-fns' interface PageProps { params: Promise<{ id: string }> } const updateRoundSchema = z .object({ name: z.string().min(1, 'Name is required').max(255), requiredReviews: z.number().int().min(1).max(10), votingStartAt: z.string().optional(), votingEndAt: z.string().optional(), }) .refine( (data) => { if (data.votingStartAt && data.votingEndAt) { return new Date(data.votingEndAt) > new Date(data.votingStartAt) } return true }, { message: 'End date must be after start date', path: ['votingEndAt'], } ) type UpdateRoundForm = z.infer // Convert ISO date to datetime-local format function toDatetimeLocal(date: Date | string | null | undefined): string { if (!date) return '' const d = new Date(date) // Format: YYYY-MM-DDTHH:mm return format(d, "yyyy-MM-dd'T'HH:mm") } function EditRoundContent({ roundId }: { roundId: string }) { const router = useRouter() const [criteria, setCriteria] = useState([]) const [criteriaInitialized, setCriteriaInitialized] = useState(false) const [roundType, setRoundType] = useState<'FILTERING' | 'EVALUATION' | 'LIVE_EVENT'>('EVALUATION') const [roundSettings, setRoundSettings] = useState>({}) // Fetch round data const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId, }) // Fetch evaluation form const { data: evaluationForm, isLoading: loadingForm } = trpc.round.getEvaluationForm.useQuery({ roundId }) // Check if evaluations exist const { data: hasEvaluations } = trpc.round.hasEvaluations.useQuery({ roundId, }) // Mutations const updateRound = trpc.round.update.useMutation({ onSuccess: () => { router.push(`/admin/rounds/${roundId}`) }, }) const updateEvaluationForm = trpc.round.updateEvaluationForm.useMutation() // Initialize form with existing data const form = useForm({ resolver: zodResolver(updateRoundSchema), defaultValues: { name: '', requiredReviews: 3, votingStartAt: '', votingEndAt: '', }, }) // Update form when round data loads useEffect(() => { if (round) { form.reset({ name: round.name, requiredReviews: round.requiredReviews, votingStartAt: toDatetimeLocal(round.votingStartAt), votingEndAt: toDatetimeLocal(round.votingEndAt), }) // Set round type and settings setRoundType((round.roundType as typeof roundType) || 'EVALUATION') setRoundSettings((round.settingsJson as Record) || {}) } }, [round, form]) // Initialize criteria from evaluation form useEffect(() => { if (evaluationForm && !criteriaInitialized) { const existingCriteria = evaluationForm.criteriaJson as unknown as Criterion[] if (Array.isArray(existingCriteria)) { setCriteria(existingCriteria) } setCriteriaInitialized(true) } else if (!loadingForm && !evaluationForm && !criteriaInitialized) { setCriteriaInitialized(true) } }, [evaluationForm, loadingForm, criteriaInitialized]) const onSubmit = async (data: UpdateRoundForm) => { // Update round with type and settings await updateRound.mutateAsync({ id: roundId, name: data.name, requiredReviews: data.requiredReviews, roundType, settingsJson: roundSettings, votingStartAt: data.votingStartAt ? new Date(data.votingStartAt) : null, votingEndAt: data.votingEndAt ? new Date(data.votingEndAt) : null, }) // Update evaluation form if criteria changed and no evaluations exist if (!hasEvaluations && criteria.length > 0) { await updateEvaluationForm.mutateAsync({ roundId, criteria, }) } } const isLoading = loadingRound || loadingForm if (isLoading) { return } if (!round) { return (

Round Not Found

) } const isPending = updateRound.isPending || updateEvaluationForm.isPending const isActive = round.status === 'ACTIVE' return (
{/* Header */}

Edit Round

{round.status}
{/* Form */}
{/* Basic Information */} Basic Information ( Round Name )} /> ( Required Reviews per Project field.onChange(parseInt(e.target.value) || 1) } /> Minimum number of evaluations each project should receive )} /> {/* Round Type & Settings */} {/* Voting Window */} Voting Window Set when jury members can submit their evaluations {isActive && (

This round is active. Changing the voting window may affect ongoing evaluations.

)}
( Start Date & Time )} /> ( End Date & Time )} />

Leave empty to disable the voting window enforcement.

{/* Evaluation Criteria */} Evaluation Criteria Define the criteria jurors will use to evaluate projects {hasEvaluations ? (

Criteria cannot be modified after evaluations have been submitted. {criteria.length} criteria defined.

{}} disabled={true} />
) : ( )}
{/* Error Display */} {(updateRound.error || updateEvaluationForm.error) && (

{updateRound.error?.message || updateEvaluationForm.error?.message}

)} {/* Actions */}
) } function EditRoundSkeleton() { return (
) } export default function EditRoundPage({ params }: PageProps) { const { id } = use(params) return ( }> ) }