Performance optimization, applicant portal, and missing DB migration

Performance:
- Convert admin dashboard from SSR to client-side tRPC (fixes 503/ChunkLoadError)
- New dashboard.getStats tRPC endpoint batches 16 queries into single response
- Parallelize jury dashboard queries (assignments + gracePeriods via Promise.all)
- Add project.getFullDetail combined endpoint (project + assignments + stats)
- Configure Prisma connection pool (connection_limit=20, pool_timeout=10)
- Add optimizePackageImports for lucide-react tree-shaking
- Increase React Query staleTime from 1min to 5min

Applicant portal:
- Add applicant layout, nav, dashboard, documents, team, and mentor pages
- Add applicant router with document and team management endpoints
- Add chunk error recovery utility
- Update role nav and auth redirect for applicant role

Database:
- Add migration for missing schema elements (SpecialAward job tracking
  columns, WizardTemplate table, missing indexes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 11:04:26 +01:00
parent 09091d7c08
commit 98f4a957cc
32 changed files with 3002 additions and 1121 deletions

View File

@@ -0,0 +1,184 @@
import { z } from 'zod'
import { router, adminProcedure } from '../trpc'
export const dashboardRouter = router({
/**
* Get all dashboard stats in a single query batch.
* Replaces the 16 parallel Prisma queries that were previously
* run during SSR, which blocked the event loop and caused 503s.
*/
getStats: adminProcedure
.input(z.object({ editionId: z.string() }))
.query(async ({ ctx, input }) => {
const { editionId } = input
const edition = await ctx.prisma.program.findUnique({
where: { id: editionId },
select: { name: true, year: true },
})
if (!edition) return null
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
const [
activeRoundCount,
totalRoundCount,
projectCount,
newProjectsThisWeek,
totalJurors,
activeJurors,
evaluationStats,
totalAssignments,
recentRounds,
latestProjects,
categoryBreakdown,
oceanIssueBreakdown,
recentActivity,
pendingCOIs,
draftRounds,
unassignedProjects,
] = await Promise.all([
ctx.prisma.round.count({
where: { programId: editionId, status: 'ACTIVE' },
}),
ctx.prisma.round.count({
where: { programId: editionId },
}),
ctx.prisma.project.count({
where: { programId: editionId },
}),
ctx.prisma.project.count({
where: {
programId: editionId,
createdAt: { gte: sevenDaysAgo },
},
}),
ctx.prisma.user.count({
where: {
role: 'JURY_MEMBER',
status: { in: ['ACTIVE', 'INVITED', 'NONE'] },
assignments: { some: { round: { programId: editionId } } },
},
}),
ctx.prisma.user.count({
where: {
role: 'JURY_MEMBER',
status: 'ACTIVE',
assignments: { some: { round: { programId: editionId } } },
},
}),
ctx.prisma.evaluation.groupBy({
by: ['status'],
where: { assignment: { round: { programId: editionId } } },
_count: true,
}),
ctx.prisma.assignment.count({
where: { round: { programId: editionId } },
}),
ctx.prisma.round.findMany({
where: { programId: editionId },
orderBy: { createdAt: 'desc' },
take: 5,
select: {
id: true,
name: true,
status: true,
votingStartAt: true,
votingEndAt: true,
submissionEndDate: true,
_count: {
select: {
projects: true,
assignments: true,
},
},
assignments: {
select: {
evaluation: { select: { status: true } },
},
},
},
}),
ctx.prisma.project.findMany({
where: { programId: editionId },
orderBy: { createdAt: 'desc' },
take: 8,
select: {
id: true,
title: true,
teamName: true,
country: true,
competitionCategory: true,
oceanIssue: true,
logoKey: true,
createdAt: true,
submittedAt: true,
status: true,
round: { select: { name: true } },
},
}),
ctx.prisma.project.groupBy({
by: ['competitionCategory'],
where: { programId: editionId },
_count: true,
}),
ctx.prisma.project.groupBy({
by: ['oceanIssue'],
where: { programId: editionId },
_count: true,
}),
ctx.prisma.auditLog.findMany({
where: {
timestamp: { gte: sevenDaysAgo },
},
orderBy: { timestamp: 'desc' },
take: 8,
select: {
id: true,
action: true,
entityType: true,
timestamp: true,
user: { select: { name: true } },
},
}),
ctx.prisma.conflictOfInterest.count({
where: {
hasConflict: true,
reviewedAt: null,
assignment: { round: { programId: editionId } },
},
}),
ctx.prisma.round.count({
where: { programId: editionId, status: 'DRAFT' },
}),
ctx.prisma.project.count({
where: {
programId: editionId,
round: { status: 'ACTIVE' },
assignments: { none: {} },
},
}),
])
return {
edition,
activeRoundCount,
totalRoundCount,
projectCount,
newProjectsThisWeek,
totalJurors,
activeJurors,
evaluationStats,
totalAssignments,
recentRounds,
latestProjects,
categoryBreakdown,
oceanIssueBreakdown,
recentActivity,
pendingCOIs,
draftRounds,
unassignedProjects,
}
}),
})