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:
@@ -1059,39 +1059,77 @@ model LiveVotingSession {
|
||||
votingEndsAt DateTime?
|
||||
projectOrderJson Json? @db.JsonB // Array of project IDs in presentation order
|
||||
|
||||
// Criteria-based voting
|
||||
votingMode String @default("simple") // "simple" (1-10) | "criteria" (per-criterion scores)
|
||||
criteriaJson Json? @db.JsonB // Array of { id, label, description, scale, weight }
|
||||
|
||||
// Audience & presentation settings
|
||||
allowAudienceVotes Boolean @default(false)
|
||||
audienceVoteWeight Float @default(0) // 0.0 to 1.0
|
||||
tieBreakerMethod String @default("admin_decides") // 'admin_decides' | 'highest_individual' | 'revote'
|
||||
presentationSettingsJson Json? @db.JsonB
|
||||
|
||||
// Audience voting configuration
|
||||
audienceVotingMode String @default("disabled") // "disabled" | "per_project" | "per_category" | "favorites"
|
||||
audienceMaxFavorites Int @default(3) // For "favorites" mode
|
||||
audienceRequireId Boolean @default(false) // Require email/phone for audience
|
||||
audienceVotingDuration Int? // Minutes (null = same as jury)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
votes LiveVote[]
|
||||
audienceVoters AudienceVoter[]
|
||||
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
model LiveVote {
|
||||
id String @id @default(cuid())
|
||||
sessionId String
|
||||
projectId String
|
||||
userId String
|
||||
score Int // 1-10
|
||||
isAudienceVote Boolean @default(false)
|
||||
votedAt DateTime @default(now())
|
||||
id String @id @default(cuid())
|
||||
sessionId String
|
||||
projectId String
|
||||
userId String? // Nullable for audience voters without accounts
|
||||
score Int // 1-10 (or weighted score for criteria mode)
|
||||
isAudienceVote Boolean @default(false)
|
||||
votedAt DateTime @default(now())
|
||||
|
||||
// Criteria scores (used when votingMode="criteria")
|
||||
criterionScoresJson Json? @db.JsonB // { [criterionId]: score } - null for simple mode
|
||||
|
||||
// Audience voter link
|
||||
audienceVoterId String?
|
||||
|
||||
// Relations
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
audienceVoter AudienceVoter? @relation(fields: [audienceVoterId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([sessionId, projectId, userId])
|
||||
@@unique([sessionId, projectId, audienceVoterId])
|
||||
@@index([sessionId])
|
||||
@@index([projectId])
|
||||
@@index([userId])
|
||||
@@index([audienceVoterId])
|
||||
}
|
||||
|
||||
model AudienceVoter {
|
||||
id String @id @default(cuid())
|
||||
sessionId String
|
||||
token String @unique // Unique voting token (UUID)
|
||||
identifier String? // Optional: email, phone, or name
|
||||
identifierType String? // "email" | "phone" | "name" | "anonymous"
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
votes LiveVote[]
|
||||
|
||||
@@index([sessionId])
|
||||
@@index([token])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user