'use client' import { use, useState } from 'react' import Link from 'next/link' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' import { DeliberationRankingForm } from '@/components/jury/deliberation-ranking-form' import { LiveVotingForm } from '@/components/jury/live-voting-form' import { CheckCircle2, ChevronDown, FileText, PenLine, StickyNote } from 'lucide-react' import { toast } from 'sonner' const CATEGORY_LABEL: Record = { BUSINESS_CONCEPT: 'Business Concepts', STARTUP: 'Startups', } /** * Per-project review context during deliberation: the juror's finale scores * (revisable in place — "keep" is simply not touching them), their ceremony * notes, and a pointer to the project documents. */ function ProjectReviewCard({ project, roundId, finaleInputs, votingMode, criteria, }: { project: { id: string; title: string; teamName?: string | null } roundId: string finaleInputs: any votingMode: 'simple' | 'criteria' criteria?: Array<{ id: string; label: string; description?: string; scale: number; weight: number }> }) { const utils = trpc.useUtils() const [open, setOpen] = useState(false) const myVote = finaleInputs?.votes?.find((v: any) => v.projectId === project.id) const myNote = finaleInputs?.notes?.find((n: any) => n.projectId === project.id) const voteMutation = trpc.liveVoting.vote.useMutation({ onSuccess: () => { utils.liveVoting.getMyFinaleInputs.invalidate({ roundId }) toast.success('Score updated') }, onError: (err) => toast.error(err.message), }) return (
{project.title} {project.teamName && ( {project.teamName} )}
{myVote ? ( My score: {myVote.score}/10 ) : ( Not scored )}
{myNote?.content && (

Your ceremony notes

{myNote.content}

)}

Your grand-finale score — edit to revise, or leave as-is to keep it

{finaleInputs?.session?.id ? ( | undefined, comment: myVote.comment, } : null } onVoteSubmit={(vote) => voteMutation.mutate({ sessionId: finaleInputs.session.id, projectId: project.id, score: vote.score, criterionScores: vote.criterionScores, comment: vote.comment, }) } disabled={voteMutation.isPending} /> ) : (

No finale voting session found.

)}
) } export default function JuryDeliberationPage({ params: paramsPromise, }: { params: Promise<{ sessionId: string }> }) { const params = use(paramsPromise) const utils = trpc.useUtils() const { data: me } = trpc.user.me.useQuery() const { data: session, isLoading } = trpc.deliberation.getSession.useQuery( { sessionId: params.sessionId }, { refetchInterval: 10_000 } ) // The deliberation session points at its round; finale inputs live on the // LIVE_FINAL round's voting session — resolve via my ceremony context. const { data: ceremony } = trpc.live.getMyCeremonyContext.useQuery() const finaleRoundId = ceremony?.liveRoundId ?? null const { data: finaleInputs } = trpc.liveVoting.getMyFinaleInputs.useQuery( { roundId: finaleRoundId ?? '' }, { enabled: !!finaleRoundId } ) const [submitting, setSubmitting] = useState(false) const submitVoteMutation = trpc.deliberation.submitVote.useMutation() const handleSubmitVote = async ( votes: Array<{ projectId: string; rank?: number; isWinnerPick?: boolean }> ) => { setSubmitting(true) try { for (const vote of votes) { await submitVoteMutation.mutateAsync({ sessionId: params.sessionId, projectId: vote.projectId, rank: vote.rank, isWinnerPick: vote.isWinnerPick, }) } toast.success('Your ranking has been submitted') utils.deliberation.getSession.invalidate({ sessionId: params.sessionId }) } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to submit vote') } finally { setSubmitting(false) } } if (isLoading || !me) { return (

Loading session…

) } if (!session) { return (

Session not found

) } const isParticipant = (session.participants ?? []).some( (p: any) => p.user?.user?.id === me.id ) const hasVoted = (session.votes ?? []).some( (v: any) => v.juryMember?.user?.id === me.id && v.runoffRound === 0 ) const projects = ((session as any).projects ?? []) as Array<{ id: string title: string teamName?: string | null }> const votingMode = (finaleInputs?.session?.votingMode ?? 'simple') as 'simple' | 'criteria' const criteria = finaleInputs?.session?.criteriaJson as | Array<{ id: string; label: string; description?: string; scale: number; weight: number }> | undefined const header = (
Deliberation — {CATEGORY_LABEL[session.category] ?? session.category} {session.round?.name}
{session.status}
) const reviewSection = projects.length > 0 && finaleRoundId && (

Review Before You Rank

Your grand-finale scores, notes and the project documents — revise a score or keep it.

{projects.map((p) => ( ))}
) if (session.status !== 'VOTING' && session.status !== 'RUNOFF') { return (
{header}

{session.status === 'DELIB_OPEN' ? 'Voting has not started yet — you can already review the projects below.' : session.status === 'TALLYING' ? 'Voting is closed. Results are being tallied.' : 'This session is locked.'}

{session.status === 'DELIB_OPEN' && reviewSection}
) } if (!isParticipant) { return (
{header}

You are not a participant of this deliberation session.

) } return (
{header} {hasVoted ? (

Ranking Submitted

Thank you — the chair will review the collective result.

) : ( <> {reviewSection}

{session.mode === 'SINGLE_WINNER_VOTE' ? 'Pick Your Winner' : 'Your Ranking'}

)}
) }