Decouple projects from rounds with RoundProject join table
Projects now exist at the program level instead of being locked to a single round. A new RoundProject join table enables many-to-many relationships with per-round status tracking. Rounds have sortOrder for configurable progression paths. - Add RoundProject model, programId on Project, sortOrder on Round - Migration preserves existing data (roundId -> RoundProject entries) - Update all routers to query through RoundProject join - Add assign/remove/advance/reorder round endpoints - Add Assign, Advance, Remove Projects dialogs on round detail page - Add round reorder controls (up/down arrows) on rounds list - Show all rounds on project detail page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,11 +112,11 @@ async function DashboardStats({ editionId, sessionName }: DashboardStatsProps) {
|
||||
where: { programId: editionId },
|
||||
}),
|
||||
prisma.project.count({
|
||||
where: { round: { programId: editionId } },
|
||||
where: { programId: editionId },
|
||||
}),
|
||||
prisma.project.count({
|
||||
where: {
|
||||
round: { programId: editionId },
|
||||
programId: editionId,
|
||||
createdAt: { gte: sevenDaysAgo },
|
||||
},
|
||||
}),
|
||||
@@ -149,7 +149,7 @@ async function DashboardStats({ editionId, sessionName }: DashboardStatsProps) {
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
projects: true,
|
||||
roundProjects: true,
|
||||
assignments: true,
|
||||
},
|
||||
},
|
||||
@@ -161,31 +161,33 @@ async function DashboardStats({ editionId, sessionName }: DashboardStatsProps) {
|
||||
},
|
||||
}),
|
||||
prisma.project.findMany({
|
||||
where: { round: { programId: editionId } },
|
||||
where: { programId: editionId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 8,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
teamName: true,
|
||||
status: true,
|
||||
country: true,
|
||||
competitionCategory: true,
|
||||
oceanIssue: true,
|
||||
logoKey: true,
|
||||
createdAt: true,
|
||||
submittedAt: true,
|
||||
round: { select: { name: true } },
|
||||
roundProjects: {
|
||||
select: { status: true, round: { select: { name: true } } },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.project.groupBy({
|
||||
by: ['competitionCategory'],
|
||||
where: { round: { programId: editionId } },
|
||||
where: { programId: editionId },
|
||||
_count: true,
|
||||
}),
|
||||
prisma.project.groupBy({
|
||||
by: ['oceanIssue'],
|
||||
where: { round: { programId: editionId } },
|
||||
where: { programId: editionId },
|
||||
_count: true,
|
||||
}),
|
||||
])
|
||||
@@ -392,7 +394,7 @@ async function DashboardStats({ editionId, sessionName }: DashboardStatsProps) {
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{round._count.projects} projects · {round._count.assignments} assignments
|
||||
{round._count.roundProjects} projects · {round._count.assignments} assignments
|
||||
{round.totalEvals > 0 && (
|
||||
<> · {round.evalPercent}% evaluated</>
|
||||
)}
|
||||
@@ -459,10 +461,10 @@ async function DashboardStats({ editionId, sessionName }: DashboardStatsProps) {
|
||||
{truncate(project.title, 45)}
|
||||
</p>
|
||||
<Badge
|
||||
variant={statusColors[project.status] || 'secondary'}
|
||||
variant={statusColors[project.roundProjects[0]?.status ?? 'SUBMITTED'] || 'secondary'}
|
||||
className="shrink-0 text-[10px] px-1.5 py-0"
|
||||
>
|
||||
{project.status.replace('_', ' ')}
|
||||
{(project.roundProjects[0]?.status ?? 'SUBMITTED').replace('_', ' ')}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
|
||||
Reference in New Issue
Block a user