diff --git a/src/app/(admin)/admin/projects/page.tsx b/src/app/(admin)/admin/projects/page.tsx index 004a0d8..c151e72 100644 --- a/src/app/(admin)/admin/projects/page.tsx +++ b/src/app/(admin)/admin/projects/page.tsx @@ -622,6 +622,12 @@ export default function ProjectsPage() { AI Tags + + +
+

Project Pool

+

+ Assign unassigned projects to competition rounds +

+
- {/* Program Selector */} + {/* Filters */}
@@ -140,8 +176,8 @@ export default function ProjectPoolPage() {
- { + setCategoryFilter(value as 'STARTUP' | 'BUSINESS_CONCEPT' | 'all') setCurrentPage(1) }}> @@ -166,15 +202,32 @@ export default function ProjectPoolPage() { }} />
- - {selectedProjects.length > 0 && ( - - )}
+ {/* Action bar */} + {selectedProgramId && poolData && poolData.total > 0 && ( +
+

+ {poolData.total} unassigned project{poolData.total !== 1 ? 's' : ''} +

+
+ {selectedProjects.length > 0 && ( + + )} + +
+
+ )} + {/* Projects Table */} {selectedProgramId && ( <> @@ -195,7 +248,7 @@ export default function ProjectPoolPage() { 0} + checked={poolData.projects.length > 0 && selectedProjects.length === poolData.projects.length} onCheckedChange={toggleSelectAll} /> @@ -203,7 +256,7 @@ export default function ProjectPoolPage() { Category Country Submitted - Action + Quick Assign @@ -217,7 +270,7 @@ export default function ProjectPoolPage() {
{project.title}
@@ -225,9 +278,11 @@ export default function ProjectPoolPage() { - - {project.competitionCategory === 'STARTUP' ? 'Startup' : 'Business Concept'} - + {project.competitionCategory && ( + + {project.competitionCategory === 'STARTUP' ? 'Startup' : 'Business Concept'} + + )} {project.country || '-'} @@ -238,20 +293,20 @@ export default function ProjectPoolPage() { : '-'} - {isLoadingStages ? ( + {isLoadingRounds ? ( ) : ( + + + + + + {rounds.map((round) => ( + + {round.name} + + ))} + + +
+ + + diff --git a/src/server/routers/project-pool.ts b/src/server/routers/project-pool.ts index c1ebf09..62c8805 100644 --- a/src/server/routers/project-pool.ts +++ b/src/server/routers/project-pool.ts @@ -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 = { + 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 } + }), })