Fix evaluation criteria, jury preferences, assignment config, and dashboard stats
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:
Matt
2026-02-17 12:33:20 +01:00
parent f9016168e7
commit ef1bf24388
16 changed files with 761 additions and 588 deletions

View File

@@ -186,7 +186,8 @@ export const deliberationRouter = router({
}),
/**
* Get session with votes, results, and participants
* Get session with votes, results, and participants.
* Redacts juror identities for non-admin users when session flags are off.
*/
getSession: protectedProcedure
.input(z.object({ sessionId: z.string() }))
@@ -195,6 +196,37 @@ export const deliberationRouter = router({
if (!session) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
}
const isAdmin = ctx.user.role === 'SUPER_ADMIN' || ctx.user.role === 'PROGRAM_ADMIN'
if (isAdmin) return session
// Non-admin: enforce visibility flags
if (!session.showCollectiveRankings) {
// Anonymize juror identity on votes — only show own votes with identity
session.votes = session.votes.map((v: any, i: number) => {
const isOwn = v.juryMember?.user?.id === ctx.user.id
if (isOwn) return v
return {
...v,
juryMember: {
...v.juryMember,
user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' },
},
}
})
// Anonymize participants
session.participants = session.participants.map((p: any, i: number) => {
const isOwn = p.user?.user?.id === ctx.user.id
if (isOwn) return p
return {
...p,
user: p.user
? { ...p.user, user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' } }
: p.user,
}
})
}
return session
}),