Round detail overhaul, file requirements, project management, audit log fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m32s

- Redesign round detail page with 6 tabs (overview, projects, filtering, assignments, config, documents)
- Add jury group assignment selector in round stats bar
- Add FileRequirementsEditor component replacing SubmissionWindowManager
- Add FilteringDashboard component for AI-powered project screening
- Add project removal from rounds (single + bulk) with cascading to subsequent rounds
- Add project add/remove UI in ProjectStatesTable with confirmation dialogs
- Fix logAudit inside $transaction pattern across all 12 router files
  (PostgreSQL aborted-transaction state caused silent operation failures)
- Fix special awards creation, deletion, status update, and winner assignment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 07:49:39 +01:00
parent f572336781
commit 7f334ed095
18 changed files with 3387 additions and 968 deletions

View File

@@ -72,24 +72,25 @@ export const liveRouter = router({
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_SESSION_STARTED',
entityType: 'Round',
entityId: input.roundId,
detailsJson: {
sessionId: created.sessionId,
projectCount: input.projectOrder.length,
firstProjectId: input.projectOrder[0],
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return created
})
// Audit outside transaction so failures don't roll back the session start
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_SESSION_STARTED',
entityType: 'Round',
entityId: input.roundId,
detailsJson: {
sessionId: cursor.sessionId,
projectCount: input.projectOrder.length,
firstProjectId: input.projectOrder[0],
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return cursor
}),
@@ -123,30 +124,27 @@ export const liveRouter = router({
})
}
const updated = await ctx.prisma.$transaction(async (tx) => {
const result = await tx.liveProgressCursor.update({
where: { id: cursor.id },
data: {
activeProjectId: input.projectId,
activeOrderIndex: index,
},
})
const updated = await ctx.prisma.liveProgressCursor.update({
where: { id: cursor.id },
data: {
activeProjectId: input.projectId,
activeOrderIndex: index,
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_ACTIVE_PROJECT_SET',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
projectId: input.projectId,
orderIndex: index,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return result
// Audit outside transaction so failures don't roll back the project set
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_ACTIVE_PROJECT_SET',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
projectId: input.projectId,
orderIndex: index,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated
@@ -182,31 +180,28 @@ export const liveRouter = router({
const targetProjectId = projectOrder[input.index]
const updated = await ctx.prisma.$transaction(async (tx) => {
const result = await tx.liveProgressCursor.update({
where: { id: cursor.id },
data: {
activeProjectId: targetProjectId,
activeOrderIndex: input.index,
},
})
const updated = await ctx.prisma.liveProgressCursor.update({
where: { id: cursor.id },
data: {
activeProjectId: targetProjectId,
activeOrderIndex: input.index,
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_JUMP',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
fromIndex: cursor.activeOrderIndex,
toIndex: input.index,
projectId: targetProjectId,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return result
// Audit outside transaction so failures don't roll back the jump
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_JUMP',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
fromIndex: cursor.activeOrderIndex,
toIndex: input.index,
projectId: targetProjectId,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated
@@ -255,22 +250,23 @@ export const liveRouter = router({
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_REORDER',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
projectCount: input.projectOrder.length,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updatedCursor
})
// Audit outside transaction so failures don't roll back the reorder
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_REORDER',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: {
projectCount: input.projectOrder.length,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated
}),
@@ -291,24 +287,21 @@ export const liveRouter = router({
})
}
const updated = await ctx.prisma.$transaction(async (tx) => {
const result = await tx.liveProgressCursor.update({
where: { id: cursor.id },
data: { isPaused: true },
})
const updated = await ctx.prisma.liveProgressCursor.update({
where: { id: cursor.id },
data: { isPaused: true },
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_PAUSED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { activeProjectId: cursor.activeProjectId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return result
// Audit outside transaction so failures don't roll back the pause
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_PAUSED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { activeProjectId: cursor.activeProjectId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated
@@ -331,24 +324,21 @@ export const liveRouter = router({
})
}
const updated = await ctx.prisma.$transaction(async (tx) => {
const result = await tx.liveProgressCursor.update({
where: { id: cursor.id },
data: { isPaused: false },
})
const updated = await ctx.prisma.liveProgressCursor.update({
where: { id: cursor.id },
data: { isPaused: false },
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'LIVE_RESUMED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { activeProjectId: cursor.activeProjectId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return result
// Audit outside transaction so failures don't roll back the resume
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'LIVE_RESUMED',
entityType: 'LiveProgressCursor',
entityId: cursor.id,
detailsJson: { activeProjectId: cursor.activeProjectId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated