Multi-role members, round detail UI overhaul, dashboard jury progress, and submit bug fix
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Add roles UserRole[] to User model with migration + backfill from existing role column
- Update auth JWT/session to propagate roles array with [role] fallback for stale tokens
- Update tRPC hasRole() middleware and add userHasRole() helper for inline role checks
- Update ~15 router inline checks and ~13 DB queries to use roles array
- Add updateRoles admin mutation with SUPER_ADMIN guard and priority-based primary role
- Add role switcher UI in admin sidebar and role-nav for multi-role users
- Remove redundant stats cards from round detail, add window dates to header banner
- Merge Members section into JuryProgressTable with inline cap editor and remove buttons
- Reorder round detail assignments tab: Progress > Score Dist > Assignments > Coverage > Jury Group
- Make score distribution fill full vertical height, reassignment history always open
- Add per-juror progress bars to admin dashboard ActiveRoundPanel for EVALUATION rounds
- Fix evaluation submit bug: use isSubmitting state instead of startMutation.isPending

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 17:44:55 +01:00
parent 230347005c
commit f3fd9eebee
25 changed files with 963 additions and 714 deletions

View File

@@ -41,6 +41,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
const evaluationIdRef = useRef<string | null>(null)
const isSubmittedRef = useRef(false)
const isSubmittingRef = useRef(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const autosaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null)
@@ -318,10 +319,12 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
autosaveTimerRef.current = null
}
isSubmittingRef.current = true
setIsSubmitting(true)
if (!myAssignment) {
toast.error('Assignment not found')
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
@@ -335,16 +338,19 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
if (c.type === 'numeric' && (val === undefined || val === null)) {
toast.error(`Please score "${c.label}"`)
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
if (c.type === 'boolean' && val === undefined) {
toast.error(`Please answer "${c.label}"`)
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
if (c.type === 'text' && (!val || (typeof val === 'string' && !val.trim()))) {
toast.error(`Please fill in "${c.label}"`)
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
}
@@ -355,6 +361,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
if (isNaN(score) || score < 1 || score > 10) {
toast.error('Please enter a valid score between 1 and 10')
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
}
@@ -363,6 +370,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
if (!binaryDecision) {
toast.error('Please select accept or reject')
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
}
@@ -371,6 +379,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
if (!feedbackText.trim() || feedbackText.length < feedbackMinLength) {
toast.error(`Please provide feedback (minimum ${feedbackMinLength} characters)`)
isSubmittingRef.current = false
setIsSubmitting(false)
return
}
}
@@ -414,6 +423,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
} catch {
// Error toast already handled by onError callback
isSubmittingRef.current = false
setIsSubmitting(false)
}
}
@@ -878,7 +888,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
</Button>
<Button
onClick={handleSubmit}
disabled={submitMutation.isPending || startMutation.isPending}
disabled={submitMutation.isPending || isSubmitting}
className="bg-brand-blue hover:bg-brand-blue-light"
>
<Send className="mr-2 h-4 w-4" />