Fix voting gate to use round status, make eval doc uploads toggleable
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m26s

Voting check now uses round.status === ROUND_ACTIVE instead of requiring
windowOpenAt/windowCloseAt date range, fixing manual open/reopen scenarios.
Added requireDocumentUpload toggle (default off) to evaluation round config
so rounds reusing prior-round documents don't need file requirements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-17 14:13:25 +01:00
parent cef4709444
commit a62f511d7f
6 changed files with 53 additions and 49 deletions

View File

@@ -468,16 +468,18 @@ export default function RoundDetailPage() {
action: undefined as Route | undefined,
actionLabel: undefined as string | undefined,
},
{
label: 'File requirements set',
ready: (fileRequirements?.length ?? 0) > 0,
detail:
(fileRequirements?.length ?? 0) > 0
? `${fileRequirements?.length} requirement(s)`
: 'No file requirements \u2014 configure in Config tab',
action: undefined as Route | undefined,
actionLabel: undefined as string | undefined,
},
...((isEvaluation && !(config.requireDocumentUpload as boolean))
? []
: [{
label: 'File requirements set',
ready: (fileRequirements?.length ?? 0) > 0,
detail:
(fileRequirements?.length ?? 0) > 0
? `${fileRequirements?.length} requirement(s)`
: 'No file requirements \u2014 configure in Config tab',
action: undefined as Route | undefined,
actionLabel: undefined as string | undefined,
}]),
]
const readyCount = readinessItems.filter((i) => i.ready).length
@@ -1738,25 +1740,27 @@ export default function RoundDetailPage() {
{/* Evaluation Criteria Editor (EVALUATION rounds only) */}
{isEvaluation && <EvaluationCriteriaEditor roundId={roundId} />}
{/* Document Requirements */}
<Card>
<CardHeader>
<CardTitle className="text-base">Document Requirements</CardTitle>
<CardDescription>
Files applicants must submit for this round
{round.windowCloseAt && (
<> &mdash; due by {new Date(round.windowCloseAt).toLocaleDateString()}</>
)}
</CardDescription>
</CardHeader>
<CardContent>
<FileRequirementsEditor
roundId={roundId}
windowOpenAt={round.windowOpenAt}
windowCloseAt={round.windowCloseAt}
/>
</CardContent>
</Card>
{/* Document Requirements — hidden for EVALUATION rounds unless requireDocumentUpload is on */}
{(!isEvaluation || !!(config.requireDocumentUpload as boolean)) && (
<Card>
<CardHeader>
<CardTitle className="text-base">Document Requirements</CardTitle>
<CardDescription>
Files applicants must submit for this round
{round.windowCloseAt && (
<> &mdash; due by {new Date(round.windowCloseAt).toLocaleDateString()}</>
)}
</CardDescription>
</CardHeader>
<CardContent>
<FileRequirementsEditor
roundId={roundId}
windowOpenAt={round.windowOpenAt}
windowCloseAt={round.windowCloseAt}
/>
</CardContent>
</Card>
)}
</TabsContent>
{/* ═══════════ AWARDS TAB ═══════════ */}

View File

@@ -299,14 +299,10 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
)
}
// Check if round is active and voting window is open
const now = new Date()
// Check if round is active — round status is the primary gate for evaluations
const isRoundActive = round.status === 'ROUND_ACTIVE'
const isWindowOpen = isRoundActive &&
round.windowOpenAt && round.windowCloseAt &&
new Date(round.windowOpenAt) <= now && new Date(round.windowCloseAt) >= now
if (!isWindowOpen) {
if (!isRoundActive) {
return (
<div className="space-y-6">
<div className="flex items-center gap-4">
@@ -325,9 +321,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
<div>
<h2 className="text-lg font-semibold">Evaluation Not Available</h2>
<p className="text-sm text-muted-foreground mt-1">
{!isRoundActive
? 'This round is not currently active. Evaluations can only be submitted during an active round.'
: 'The voting window for this round is not currently open. Please check back when the window opens.'}
This round is not currently active. Evaluations can only be submitted during an active round.
</p>
<Button variant="outline" size="sm" className="mt-4" asChild>
<Link href={`/jury/competitions/${roundId}/projects/${projectId}` as Route}>

View File

@@ -27,11 +27,8 @@ export default function JuryProjectDetailPage() {
{ enabled: !!roundId }
)
// Determine if voting is currently open
const now = new Date()
const isVotingOpen = round?.status === 'ROUND_ACTIVE' &&
round?.windowOpenAt && round?.windowCloseAt &&
new Date(round.windowOpenAt) <= now && new Date(round.windowCloseAt) >= now
// Round status is the primary gate for evaluations
const isVotingOpen = round?.status === 'ROUND_ACTIVE'
if (isLoading) {
return (

View File

@@ -357,12 +357,7 @@ async function JuryDashboardContent() {
const evaluation = assignment.evaluation
const isCompleted = evaluation?.status === 'SUBMITTED'
const isDraft = evaluation?.status === 'DRAFT'
const isVotingOpen =
assignment.round.status === 'ROUND_ACTIVE' &&
assignment.round.windowOpenAt &&
assignment.round.windowCloseAt &&
new Date(assignment.round.windowOpenAt) <= now &&
new Date(assignment.round.windowCloseAt) >= now
const isVotingOpen = assignment.round.status === 'ROUND_ACTIVE'
return (
<div

View File

@@ -143,6 +143,18 @@ export function EvaluationConfig({ config, onChange }: EvaluationConfigProps) {
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label htmlFor="requireDocumentUpload">Require Document Upload</Label>
<p className="text-xs text-muted-foreground">Applicants must upload documents for this evaluation round (disable if documents were uploaded in a previous round)</p>
</div>
<Switch
id="requireDocumentUpload"
checked={(config.requireDocumentUpload as boolean) ?? false}
onCheckedChange={(v) => update('requireDocumentUpload', v)}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label htmlFor="peerReviewEnabled">Peer Review</Label>

View File

@@ -83,6 +83,8 @@ export const EvaluationConfigSchema = z.object({
feedbackMinLength: z.number().int().nonnegative().default(0),
requireAllCriteriaScored: z.boolean().default(true),
requireDocumentUpload: z.boolean().default(false),
coiRequired: z.boolean().default(true),
peerReviewEnabled: z.boolean().default(false),