Round system redesign: criteria voting, audience voting, pipeline view, and admin UX improvements

- Schema: Extend LiveVotingSession with votingMode, criteriaJson, audience fields;
  add AudienceVoter model; make LiveVote.userId nullable for audience voters
- Backend: Criteria-based voting with weighted scores, audience registration/voting
  with token-based dedup, configurable jury/audience weight in results
- Jury UI: Criteria scoring with per-criterion sliders alongside simple 1-10 mode
- Public audience voting page at /vote/[sessionId] with mobile-first design
- Admin live voting: Tabbed layout (Session/Config/Results), criteria config,
  audience settings, weight-adjustable results with tie detection
- Round type settings: Visual card selector replacing dropdown, feature tags
- Round detail page: Live event status section, type-specific stats and actions
- Round pipeline view: Horizontal visualization with bottleneck detection,
  List/Pipeline toggle on rounds page
- SSE: Separate jury/audience vote events, audience vote tracking
- Field visibility: Hide irrelevant fields per round type in create/edit forms

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 14:27:49 +01:00
parent b5d90d3c26
commit 2a5fa463b3
14 changed files with 2518 additions and 456 deletions

View File

@@ -38,6 +38,13 @@ export interface LiveEventRoundSettings {
votingWindowSeconds: number
showLiveScores: boolean
allowVoteChange: boolean
votingMode: 'simple' | 'criteria'
// Audience voting
audienceVotingMode: 'disabled' | 'per_project' | 'per_category' | 'favorites'
audienceMaxFavorites: number
audienceRequireId: boolean
audienceVotingDuration: number | null
// Display
displayMode: 'SCORES' | 'RANKING' | 'NONE'
@@ -74,6 +81,11 @@ export const defaultLiveEventSettings: LiveEventRoundSettings = {
votingWindowSeconds: 30,
showLiveScores: true,
allowVoteChange: false,
votingMode: 'simple',
audienceVotingMode: 'disabled',
audienceMaxFavorites: 3,
audienceRequireId: false,
audienceVotingDuration: null,
displayMode: 'RANKING',
}
@@ -90,3 +102,43 @@ export const roundTypeDescriptions: Record<string, string> = {
EVALUATION: 'In-depth evaluation with detailed criteria and feedback',
LIVE_EVENT: 'Real-time voting during presentations',
}
// Field visibility per round type
export const ROUND_FIELD_VISIBILITY: Record<string, {
showRequiredReviews: boolean
showAssignmentLimits: boolean
showVotingWindow: boolean
showSubmissionDates: boolean
showEvaluationForm: boolean
}> = {
FILTERING: {
showRequiredReviews: false,
showAssignmentLimits: false,
showVotingWindow: false,
showSubmissionDates: true,
showEvaluationForm: false,
},
EVALUATION: {
showRequiredReviews: true,
showAssignmentLimits: true,
showVotingWindow: true,
showSubmissionDates: true,
showEvaluationForm: true,
},
LIVE_EVENT: {
showRequiredReviews: false,
showAssignmentLimits: false,
showVotingWindow: false,
showSubmissionDates: false,
showEvaluationForm: false,
},
}
// Live voting criterion type
export interface LiveVotingCriterion {
id: string
label: string
description?: string
scale: number // max score (e.g. 10)
weight: number // 0-1, weights must sum to 1
}