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:
2026-02-02 22:33:55 +01:00
parent 0d2bc4db7e
commit fd5e5222da
52 changed files with 1892 additions and 326 deletions

View File

@@ -56,7 +56,6 @@ async function AssignmentsContent({
title: true,
teamName: true,
description: true,
status: true,
files: {
select: {
id: true,

View File

@@ -45,7 +45,6 @@ async function JuryDashboardContent() {
id: true,
title: true,
teamName: true,
status: true,
},
},
round: {

View File

@@ -34,11 +34,18 @@ async function EvaluateContent({ projectId }: { projectId: string }) {
redirect('/login')
}
// Get project with assignment info for this user
const project = await prisma.project.findUnique({
where: { id: projectId },
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
files: true,
evaluation: {
include: {
form: true,
},
},
round: {
include: {
program: {
@@ -53,25 +60,18 @@ async function EvaluateContent({ projectId }: { projectId: string }) {
},
})
// Get project details
const project = await prisma.project.findUnique({
where: { id: projectId },
include: {
files: true,
},
})
if (!project) {
notFound()
}
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
evaluation: {
include: {
form: true,
},
},
},
})
if (!assignment) {
return (
<div className="space-y-6">
@@ -95,7 +95,7 @@ async function EvaluateContent({ projectId }: { projectId: string }) {
)
}
const round = project.round
const round = assignment.round
const now = new Date()
// Check voting window

View File

@@ -49,10 +49,18 @@ async function EvaluationContent({ projectId }: { projectId: string }) {
redirect('/login')
}
// Get project with assignment info for this user
const project = await prisma.project.findUnique({
where: { id: projectId },
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
evaluation: {
include: {
form: true,
},
},
round: {
include: {
program: {
@@ -67,25 +75,20 @@ async function EvaluationContent({ projectId }: { projectId: string }) {
},
})
// Get project details
const project = await prisma.project.findUnique({
where: { id: projectId },
select: {
id: true,
title: true,
teamName: true,
},
})
if (!project) {
notFound()
}
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
evaluation: {
include: {
form: true,
},
},
},
})
if (!assignment) {
return (
<div className="space-y-6">
@@ -145,7 +148,7 @@ async function EvaluationContent({ projectId }: { projectId: string }) {
const criterionScores =
(evaluation.criterionScoresJson as unknown as Record<string, number>) || {}
const round = project.round
const round = assignment.round
return (
<div className="space-y-6">

View File

@@ -43,11 +43,14 @@ async function ProjectContent({ projectId }: { projectId: string }) {
redirect('/login')
}
// Get project with assignment info for this user
const project = await prisma.project.findUnique({
where: { id: projectId },
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
files: true,
evaluation: true,
round: {
include: {
program: {
@@ -62,21 +65,18 @@ async function ProjectContent({ projectId }: { projectId: string }) {
},
})
// Get project details
const project = await prisma.project.findUnique({
where: { id: projectId },
include: {
files: true,
},
})
if (!project) {
notFound()
}
// Check if user is assigned to this project
const assignment = await prisma.assignment.findFirst({
where: {
projectId,
userId,
},
include: {
evaluation: true,
},
})
if (!assignment) {
// User is not assigned to this project
return (
@@ -99,7 +99,7 @@ async function ProjectContent({ projectId }: { projectId: string }) {
}
const evaluation = assignment.evaluation
const round = project.round
const round = assignment.round
const now = new Date()
// Check voting window