feat: admin can fill in evaluations on behalf of jurors
All checks were successful
Build and Push Docker Image / build (push) Successful in 11m54s

When a juror cannot connect during an evaluation round, an admin
can now submit evaluations for them.

Router — new admin procedures:
- adminStart / adminAutosave: create and save drafts for any juror.
- adminSubmitOnBehalf: submit bypassing ROUND_ACTIVE and voting-window
  checks. COI block and feedback/criterion validation still enforced.
  Audit log records both admin and juror IDs plus bypassedWindow flag.
- getJurorAssignmentsForRound: list a juror's assignments + eval state.

UI — two new admin pages under /admin/rounds/[roundId]/jurors/[userId]/:
- evaluate: list of pending + completed assignments, COI flagged.
- evaluate/[projectId]: evaluation form reusing the juror's scoring UI,
  with an "acting on behalf" banner and confirmation dialog before
  submit. Back button returns to the assignments list.

Entry point: FilePen icon on each juror row in JuryProgressTable.

Refactor: extracted the scoring form JSX into shared
EvaluationFormFields component so the juror page and the admin proxy
page render identical inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-21 16:41:14 +02:00
parent fd4f6dde16
commit 9cb3b9de13
6 changed files with 1547 additions and 358 deletions

View File

@@ -24,7 +24,9 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog'
import { Loader2, Mail, ArrowRightLeft, UserPlus, Trash2 } from 'lucide-react'
import { Loader2, Mail, ArrowRightLeft, UserPlus, Trash2, FilePen } from 'lucide-react'
import Link from 'next/link'
import type { Route } from 'next'
import { TransferAssignmentsDialog } from './transfer-assignments-dialog'
import { InlineMemberCap } from '@/components/admin/jury/inline-member-cap'
@@ -186,6 +188,24 @@ export function JuryProgressTable({
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-5 w-5 text-muted-foreground hover:text-foreground"
asChild
>
<Link href={`/admin/rounds/${roundId}/jurors/${juror.id}/evaluate` as Route}>
<FilePen className="h-3 w-3" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent side="left"><p>Fill in evaluations on behalf of this juror</p></TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>