feat(finale): full ceremony UI — admin console, jury phases+notes, deliberation flow, audience voting, big-screen view
- Admin Ceremony tab: phase driver with real server timers + overtime, run order with category grouping + send-to-screens, audience windows + QR dialog, override slides, timing log, results reveal builder/stepper - Admin Deliberation tab: per-category session creation from the round's jury group, open/close voting, tally/runoff/override/finalize - Jury live page: ON_DECK/PRESENTING/QA/SCORING aware, autosaved notes, vote comments; LiveVotingForm fixed (criteria score >10 rejection bug, permanent lock-out after submit) and made revisable - Jury deliberation page: identityless submitVote, review-before-rank context (finale scores editable, ceremony notes, docs link) - Jury nav: Live Ceremony + per-category Deliberation links - Public: rebuilt QR audience voting page (anonymous-safe, favorite-pick windows, change-until-close), new /live/ceremony/[roundId] projector view with animated reveal, confetti, audience QR slide, override slides Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ type TimingEntry = {
|
||||
startedAt: string
|
||||
endedAt: string
|
||||
configuredSeconds: number | null
|
||||
elapsedSeconds: number // pause-adjusted actual duration
|
||||
overranSeconds: number
|
||||
}
|
||||
|
||||
@@ -34,6 +35,7 @@ function closedOutTiming(cursor: LiveProgressCursor, now: Date): Prisma.InputJso
|
||||
startedAt: cursor.phaseStartedAt.toISOString(),
|
||||
endedAt: now.toISOString(),
|
||||
configuredSeconds: cursor.phaseDurationSeconds,
|
||||
elapsedSeconds: elapsedSec,
|
||||
overranSeconds:
|
||||
cursor.phaseDurationSeconds == null
|
||||
? 0
|
||||
@@ -714,6 +716,31 @@ export const liveRouter = router({
|
||||
})
|
||||
}),
|
||||
|
||||
/**
|
||||
* Ceremony navigation context for jurors: the active live round (if a
|
||||
* ceremony cursor exists) and any deliberation sessions they participate in.
|
||||
*/
|
||||
getMyCeremonyContext: protectedProcedure.query(async ({ ctx }) => {
|
||||
const cursor = await ctx.prisma.liveProgressCursor.findFirst({
|
||||
where: { round: { roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE' } },
|
||||
select: { roundId: true },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
})
|
||||
const participations = await ctx.prisma.deliberationParticipant.findMany({
|
||||
where: {
|
||||
user: { userId: ctx.user.id },
|
||||
session: { status: { in: ['DELIB_OPEN', 'VOTING', 'RUNOFF'] } },
|
||||
},
|
||||
select: {
|
||||
session: { select: { id: true, category: true, status: true } },
|
||||
},
|
||||
})
|
||||
return {
|
||||
liveRoundId: cursor?.roundId ?? null,
|
||||
deliberations: participations.map((p) => p.session),
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get current cursor state (for all users, including audience)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user