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

@@ -36,26 +36,23 @@ export const juryGroupRouter = router({
const { defaultCategoryQuotas, ...rest } = input
const juryGroup = await ctx.prisma.$transaction(async (tx) => {
const created = await tx.juryGroup.create({
data: {
...rest,
defaultCategoryQuotas: defaultCategoryQuotas ?? undefined,
},
})
const juryGroup = await ctx.prisma.juryGroup.create({
data: {
...rest,
defaultCategoryQuotas: defaultCategoryQuotas ?? undefined,
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'CREATE',
entityType: 'JuryGroup',
entityId: created.id,
detailsJson: { name: input.name, competitionId: input.competitionId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return created
// Audit outside transaction so failures don't roll back the create
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'CREATE',
entityType: 'JuryGroup',
entityId: juryGroup.id,
detailsJson: { name: input.name, competitionId: input.competitionId },
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return juryGroup
@@ -187,39 +184,36 @@ export const juryGroupRouter = router({
})
}
const member = await ctx.prisma.$transaction(async (tx) => {
const created = await tx.juryGroupMember.create({
data: {
juryGroupId: input.juryGroupId,
userId: input.userId,
role: input.role,
maxAssignmentsOverride: input.maxAssignmentsOverride ?? undefined,
capModeOverride: input.capModeOverride ?? undefined,
categoryQuotasOverride: input.categoryQuotasOverride ?? undefined,
preferredStartupRatio: input.preferredStartupRatio ?? undefined,
availabilityNotes: input.availabilityNotes ?? undefined,
},
include: {
user: { select: { id: true, name: true, email: true, role: true } },
},
})
const member = await ctx.prisma.juryGroupMember.create({
data: {
juryGroupId: input.juryGroupId,
userId: input.userId,
role: input.role,
maxAssignmentsOverride: input.maxAssignmentsOverride ?? undefined,
capModeOverride: input.capModeOverride ?? undefined,
categoryQuotasOverride: input.categoryQuotasOverride ?? undefined,
preferredStartupRatio: input.preferredStartupRatio ?? undefined,
availabilityNotes: input.availabilityNotes ?? undefined,
},
include: {
user: { select: { id: true, name: true, email: true, role: true } },
},
})
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'CREATE',
entityType: 'JuryGroupMember',
entityId: created.id,
detailsJson: {
juryGroupId: input.juryGroupId,
addedUserId: input.userId,
role: input.role,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return created
// Audit outside transaction so failures don't roll back the member add
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'CREATE',
entityType: 'JuryGroupMember',
entityId: member.id,
detailsJson: {
juryGroupId: input.juryGroupId,
addedUserId: input.userId,
role: input.role,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return member
@@ -231,31 +225,28 @@ export const juryGroupRouter = router({
removeMember: adminProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
const member = await ctx.prisma.$transaction(async (tx) => {
const existing = await tx.juryGroupMember.findUniqueOrThrow({
where: { id: input.id },
})
await tx.juryGroupMember.delete({ where: { id: input.id } })
await logAudit({
prisma: tx,
userId: ctx.user.id,
action: 'DELETE',
entityType: 'JuryGroupMember',
entityId: input.id,
detailsJson: {
juryGroupId: existing.juryGroupId,
removedUserId: existing.userId,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return existing
const existing = await ctx.prisma.juryGroupMember.findUniqueOrThrow({
where: { id: input.id },
})
return member
await ctx.prisma.juryGroupMember.delete({ where: { id: input.id } })
// Audit outside transaction so failures don't roll back the member removal
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'DELETE',
entityType: 'JuryGroupMember',
entityId: input.id,
detailsJson: {
juryGroupId: existing.juryGroupId,
removedUserId: existing.userId,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return existing
}),
/**