Fix jury reminders, add notify jurors button, fix checkbox borders, widen assignment modal
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m32s

- Send Reminders button now works: added sendManualReminders() that bypasses
  cron-specific window/deadline/dedup guards so admin can send immediately
- Added Notify Jurors button that sends direct BATCH_ASSIGNED emails to all
  jurors with assignments (not dependent on NotificationEmailSetting config)
- Fixed checkbox component: default border is now neutral grey (border-input),
  red border (border-primary) only applied when checked
- Widened Add Assignment dialog from max-w-2xl to max-w-3xl to prevent overflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-19 12:15:51 +01:00
parent 51e18870b6
commit ee8b12e59c
5 changed files with 268 additions and 8 deletions

View File

@@ -85,6 +85,7 @@ import {
ShieldAlert,
Eye,
Pencil,
Mail,
} from 'lucide-react'
import {
Command,
@@ -1784,9 +1785,10 @@ export default function RoundDetailPage() {
<ScoreDistribution roundId={roundId} />
</div>
{/* Actions: Send Reminders + Export */}
{/* Actions: Send Reminders + Notify + Export */}
<div className="flex flex-wrap items-center gap-3">
<SendRemindersButton roundId={roundId} />
<NotifyJurorsButton roundId={roundId} />
<Button variant="outline" size="sm" onClick={() => setExportOpen(true)}>
<Download className="h-4 w-4 mr-1.5" />
Export Evaluations
@@ -2356,6 +2358,48 @@ function SendRemindersButton({ roundId }: { roundId: string }) {
)
}
// ── Notify Jurors of Assignments Button ──────────────────────────────────
function NotifyJurorsButton({ roundId }: { roundId: string }) {
const [open, setOpen] = useState(false)
const mutation = trpc.assignment.notifyJurorsOfAssignments.useMutation({
onSuccess: (data) => {
toast.success(`Notified ${data.jurorCount} juror(s) — ${data.emailsSent} email(s) sent`)
setOpen(false)
},
onError: (err) => toast.error(err.message),
})
return (
<>
<Button variant="outline" size="sm" onClick={() => setOpen(true)}>
<Mail className="h-4 w-4 mr-1.5" />
Notify Jurors
</Button>
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Notify jurors of their assignments?</AlertDialogTitle>
<AlertDialogDescription>
This will send an email to every juror assigned to this round, reminding them of how many projects they need to evaluate.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => mutation.mutate({ roundId })}
disabled={mutation.isPending}
>
{mutation.isPending && <Loader2 className="h-4 w-4 mr-1.5 animate-spin" />}
Notify Jurors
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
// ── Export Evaluations Dialog ─────────────────────────────────────────────
function ExportEvaluationsDialog({
@@ -2640,7 +2684,7 @@ function IndividualAssignmentsTable({
if (!open) resetDialog()
else setAddDialogOpen(true)
}}>
<DialogContent className="sm:max-w-[540px]">
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>Add Assignment</DialogTitle>
<DialogDescription>
@@ -2674,7 +2718,7 @@ function IndividualAssignmentsTable({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[480px] p-0" align="start">
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0" align="start">
<Command>
<CommandInput placeholder="Search by name or email..." />
<CommandList>
@@ -2775,7 +2819,7 @@ function IndividualAssignmentsTable({
</div>
{/* Project checklist */}
<ScrollArea className="h-[240px] rounded-md border">
<ScrollArea className="h-[320px] rounded-md border">
<div className="p-2 space-y-0.5">
{!selectedJurorId ? (
<p className="text-sm text-muted-foreground text-center py-8">