Round detail overhaul, file requirements, project management, audit log fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m32s
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:
@@ -140,4 +140,109 @@ export const roundEngineRouter = router({
|
||||
.query(async ({ ctx, input }) => {
|
||||
return getProjectRoundState(input.projectId, input.roundId, ctx.prisma)
|
||||
}),
|
||||
|
||||
/**
|
||||
* Remove a project from a round (and all subsequent rounds in that competition).
|
||||
* The project remains in all prior rounds.
|
||||
*/
|
||||
removeFromRound: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
roundId: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Get the round to know its competition and sort order
|
||||
const round = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { id: true, competitionId: true, sortOrder: true },
|
||||
})
|
||||
|
||||
// Find all rounds at this sort order or later in the same competition
|
||||
const roundsToRemoveFrom = await ctx.prisma.round.findMany({
|
||||
where: {
|
||||
competitionId: round.competitionId,
|
||||
sortOrder: { gte: round.sortOrder },
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
|
||||
const roundIds = roundsToRemoveFrom.map((r) => r.id)
|
||||
|
||||
// Delete ProjectRoundState entries for this project in all affected rounds
|
||||
const deleted = await ctx.prisma.projectRoundState.deleteMany({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
roundId: { in: roundIds },
|
||||
},
|
||||
})
|
||||
|
||||
// Check if the project is still in any round at all
|
||||
const remainingStates = await ctx.prisma.projectRoundState.count({
|
||||
where: { projectId: input.projectId },
|
||||
})
|
||||
|
||||
// If no longer in any round, reset project status back to SUBMITTED
|
||||
if (remainingStates === 0) {
|
||||
await ctx.prisma.project.update({
|
||||
where: { id: input.projectId },
|
||||
data: { status: 'SUBMITTED' },
|
||||
})
|
||||
}
|
||||
|
||||
return { success: true, removedFromRounds: deleted.count }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Batch remove projects from a round (and all subsequent rounds).
|
||||
*/
|
||||
batchRemoveFromRound: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectIds: z.array(z.string()).min(1),
|
||||
roundId: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const round = await ctx.prisma.round.findUniqueOrThrow({
|
||||
where: { id: input.roundId },
|
||||
select: { id: true, competitionId: true, sortOrder: true },
|
||||
})
|
||||
|
||||
const roundsToRemoveFrom = await ctx.prisma.round.findMany({
|
||||
where: {
|
||||
competitionId: round.competitionId,
|
||||
sortOrder: { gte: round.sortOrder },
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
|
||||
const roundIds = roundsToRemoveFrom.map((r) => r.id)
|
||||
|
||||
const deleted = await ctx.prisma.projectRoundState.deleteMany({
|
||||
where: {
|
||||
projectId: { in: input.projectIds },
|
||||
roundId: { in: roundIds },
|
||||
},
|
||||
})
|
||||
|
||||
// For projects with no remaining round states, reset to SUBMITTED
|
||||
const projectsStillInRounds = await ctx.prisma.projectRoundState.findMany({
|
||||
where: { projectId: { in: input.projectIds } },
|
||||
select: { projectId: true },
|
||||
distinct: ['projectId'],
|
||||
})
|
||||
const stillInRoundIds = new Set(projectsStillInRounds.map((p) => p.projectId))
|
||||
const orphanedIds = input.projectIds.filter((id) => !stillInRoundIds.has(id))
|
||||
|
||||
if (orphanedIds.length > 0) {
|
||||
await ctx.prisma.project.updateMany({
|
||||
where: { id: { in: orphanedIds } },
|
||||
data: { status: 'SUBMITTED' },
|
||||
})
|
||||
}
|
||||
|
||||
return { success: true, removedCount: deleted.count }
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user