feat: render advance criterion on juror evaluation page and fix related renderers

- Jury evaluate page: add prominent advance criterion block (h-14, brand-blue border) before boolean block, fix type cast to include 'advance', add advance to required-field validation
- evaluation-form.tsx: add 'advance' to CriterionType, schema, default values, progress tracking, rendering via new AdvanceCriterionField component with prominent styling
- Admin project detail: treat advance same as boolean in EvaluationDetailSheet criterion score display
- Observer project detail: treat advance same as boolean in evaluation criterion score display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 15:11:24 +01:00
parent 6c97ce3ed9
commit a327962f04
4 changed files with 148 additions and 8 deletions

View File

@@ -164,7 +164,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
id: c.id,
label: c.label,
description: c.description,
type: type as 'numeric' | 'text' | 'boolean' | 'section_header',
type: type as 'numeric' | 'text' | 'boolean' | 'advance' | 'section_header',
weight: c.weight,
minScore,
maxScore,
@@ -352,7 +352,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
setIsSubmitting(false)
return
}
if (c.type === 'boolean' && val === undefined) {
if ((c.type === 'boolean' || c.type === 'advance') && val === undefined) {
toast.error(`Please answer "${c.label}"`)
isSubmittingRef.current = false
setIsSubmitting(false)
@@ -657,6 +657,51 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
)
}
if (criterion.type === 'advance') {
const currentValue = criteriaValues[criterion.id]
return (
<div key={criterion.id} className="space-y-3 p-5 border-2 border-brand-blue/30 rounded-xl bg-brand-blue/5">
<div className="space-y-1">
<Label className="text-base font-semibold text-brand-blue">
{criterion.label}
<span className="text-destructive ml-1">*</span>
</Label>
{criterion.description && (
<p className="text-sm text-muted-foreground">{criterion.description}</p>
)}
</div>
<div className="flex gap-4">
<button
type="button"
onClick={() => handleCriterionChange(criterion.id, true)}
className={cn(
'flex-1 h-14 rounded-xl border-2 flex items-center justify-center text-base font-semibold transition-all',
currentValue === true
? 'border-emerald-500 bg-emerald-50 text-emerald-700 shadow-sm ring-2 ring-emerald-200'
: 'border-border hover:border-emerald-300 hover:bg-emerald-50/50'
)}
>
<ThumbsUp className="mr-2 h-5 w-5" />
{criterion.trueLabel || 'Yes'}
</button>
<button
type="button"
onClick={() => handleCriterionChange(criterion.id, false)}
className={cn(
'flex-1 h-14 rounded-xl border-2 flex items-center justify-center text-base font-semibold transition-all',
currentValue === false
? 'border-red-500 bg-red-50 text-red-700 shadow-sm ring-2 ring-red-200'
: 'border-border hover:border-red-300 hover:bg-red-50/50'
)}
>
<ThumbsDown className="mr-2 h-5 w-5" />
{criterion.falseLabel || 'No'}
</button>
</div>
</div>
)
}
if (criterion.type === 'boolean') {
const currentValue = criteriaValues[criterion.id]
return (