Fix evaluation criteria, jury preferences, assignment config, and dashboard stats
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m5s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m5s
- Fix criteria not showing for jurors: fetch active form independently via
getStageForm query instead of relying on existing evaluation record
- Fix scoringMode default from 'global' to 'criteria' (matching schema)
- Parse scale string format ("1-10") into minScore/maxScore for criteria display
- Fix COI dialog dismissal: prevent outside click on evaluate page Dialog
- Fix requiredReviews hardcoded to 3: read from round configJson in 4 locations
- Add jury preferences banner for unconfirmed caps on jury dashboard
- Add updateJuryPreferences tRPC procedure for self-service cap/ratio
- Simplify onboarding: always show jury step, allow cap up to 50
- Add role/ratio/availability fields to jury member invite dialog
- Simplify jury group settings (keep only defaultMaxAssignments)
- Enforce deliberation showCollectiveRankings flag for non-admin users
- Redesign dashboard stat cards: editorial data strip on mobile,
clean grid layout on desktop (no more generic card pattern)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,12 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
{ enabled: !!myAssignment?.id }
|
||||
)
|
||||
|
||||
// Fetch the active evaluation form for this round (independent of evaluation existence)
|
||||
const { data: activeForm } = trpc.evaluation.getStageForm.useQuery(
|
||||
{ roundId },
|
||||
{ enabled: !!roundId }
|
||||
)
|
||||
|
||||
// Start evaluation mutation (creates draft)
|
||||
const startMutation = trpc.evaluation.start.useMutation()
|
||||
|
||||
@@ -116,19 +122,31 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
|
||||
// Parse evaluation config from round
|
||||
const evalConfig: EvaluationConfig | null = round?.configJson as EvaluationConfig | null
|
||||
const scoringMode = evalConfig?.scoringMode ?? 'global'
|
||||
const scoringMode = evalConfig?.scoringMode ?? 'criteria'
|
||||
const requireFeedback = evalConfig?.requireFeedback ?? true
|
||||
const feedbackMinLength = evalConfig?.feedbackMinLength ?? 10
|
||||
|
||||
// Get criteria from evaluation form
|
||||
const criteria = existingEvaluation?.form?.criteriaJson as Array<{
|
||||
id: string
|
||||
label: string
|
||||
description?: string
|
||||
weight?: number
|
||||
minScore?: number
|
||||
maxScore?: number
|
||||
}> | undefined
|
||||
// Get criteria from the active evaluation form (independent of evaluation record)
|
||||
const criteria = (activeForm?.criteriaJson ?? []).map((c) => {
|
||||
// Parse scale string like "1-10" into minScore/maxScore
|
||||
let minScore = 1
|
||||
let maxScore = 10
|
||||
if (c.scale) {
|
||||
const parts = c.scale.split('-').map(Number)
|
||||
if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
|
||||
minScore = parts[0]
|
||||
maxScore = parts[1]
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: c.id,
|
||||
label: c.label,
|
||||
description: c.description,
|
||||
weight: c.weight,
|
||||
minScore,
|
||||
maxScore,
|
||||
}
|
||||
})
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
if (!myAssignment) {
|
||||
@@ -217,8 +235,12 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
// COI Dialog
|
||||
if (!coiAccepted && showCOIDialog && evalConfig?.coiRequired !== false) {
|
||||
return (
|
||||
<Dialog open={showCOIDialog} onOpenChange={setShowCOIDialog}>
|
||||
<DialogContent>
|
||||
<Dialog open={showCOIDialog} onOpenChange={() => { /* prevent dismissal */ }}>
|
||||
<DialogContent
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Conflict of Interest Declaration</DialogTitle>
|
||||
<DialogDescription className="space-y-3 pt-2">
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { formatDateOnly } from '@/lib/utils'
|
||||
import { CountdownTimer } from '@/components/shared/countdown-timer'
|
||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||
import { JuryPreferencesBanner } from '@/components/jury/preferences-banner'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function getGreeting(): string {
|
||||
@@ -757,6 +758,9 @@ export default async function JuryDashboardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Preferences banner (shown when juror has unconfirmed preferences) */}
|
||||
<JuryPreferencesBanner />
|
||||
|
||||
{/* Content */}
|
||||
<Suspense fallback={<DashboardSkeleton />}>
|
||||
<JuryDashboardContent />
|
||||
|
||||
Reference in New Issue
Block a user