Pool page: add bulk assign-to-round, enhance project pool UI

- Add assignAllToRound mutation to project-pool router
- Rewrite pool page with round selector, bulk assignment, and better layout
- Add pool navigation link to admin projects page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 07:06:59 +01:00
parent 4c0efb232c
commit 2fb26d4734
3 changed files with 228 additions and 49 deletions

View File

@@ -198,4 +198,84 @@ export const projectPoolRouter = router({
roundId,
}
}),
/**
* Assign ALL unassigned projects in a program to a round (server-side, no ID limit)
*/
assignAllToRound: adminProcedure
.input(
z.object({
programId: z.string(),
roundId: z.string(),
competitionCategory: z.enum(['STARTUP', 'BUSINESS_CONCEPT']).optional(),
})
)
.mutation(async ({ ctx, input }) => {
const { programId, roundId, competitionCategory } = input
// Verify round exists
await ctx.prisma.round.findUniqueOrThrow({
where: { id: roundId },
select: { id: true },
})
// Find all unassigned projects
const where: Record<string, unknown> = {
programId,
projectRoundStates: { none: {} },
}
if (competitionCategory) {
where.competitionCategory = competitionCategory
}
const projects = await ctx.prisma.project.findMany({
where,
select: { id: true },
})
if (projects.length === 0) {
return { success: true, assignedCount: 0, roundId }
}
const projectIds = projects.map((p) => p.id)
const result = await ctx.prisma.$transaction(async (tx) => {
await tx.projectRoundState.createMany({
data: projectIds.map((projectId) => ({ projectId, roundId })),
skipDuplicates: true,
})
const updated = await tx.project.updateMany({
where: { id: { in: projectIds } },
data: { status: 'ASSIGNED' },
})
await tx.projectStatusHistory.createMany({
data: projectIds.map((projectId) => ({
projectId,
status: 'ASSIGNED',
changedBy: ctx.user?.id,
})),
})
await logAudit({
prisma: tx,
userId: ctx.user?.id,
action: 'BULK_ASSIGN_ALL_TO_ROUND',
entityType: 'Project',
detailsJson: {
roundId,
programId,
competitionCategory: competitionCategory || 'ALL',
projectCount: projectIds.length,
},
ipAddress: ctx.ip,
userAgent: ctx.userAgent,
})
return updated
})
return { success: true, assignedCount: result.count, roundId }
}),
})