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:
@@ -96,7 +96,7 @@ export default function ProjectAssignmentsPage() {
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link href={`/admin/rounds/${project?.roundId}/assignments`}>
|
||||
<Link href={`/admin/rounds/${project?.roundProjects?.[0]?.round?.id}/assignments`}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Manage in Round
|
||||
</Link>
|
||||
|
||||
@@ -121,7 +121,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||
|
||||
// Fetch existing tags for suggestions
|
||||
const { data: existingTags } = trpc.project.getTags.useQuery({
|
||||
roundId: project?.roundId,
|
||||
roundId: project?.roundProjects?.[0]?.round?.id,
|
||||
})
|
||||
|
||||
// Mutations
|
||||
@@ -162,7 +162,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||
title: project.title,
|
||||
teamName: project.teamName || '',
|
||||
description: project.description || '',
|
||||
status: project.status as UpdateProjectForm['status'],
|
||||
status: (project.roundProjects?.[0]?.status ?? 'SUBMITTED') as UpdateProjectForm['status'],
|
||||
tags: project.tags || [],
|
||||
})
|
||||
}
|
||||
@@ -197,6 +197,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||
teamName: data.teamName || null,
|
||||
description: data.description || null,
|
||||
status: data.status,
|
||||
roundId: project?.roundProjects?.[0]?.round?.id,
|
||||
tags: data.tags,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,20 +139,29 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||
fallback="initials"
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Link
|
||||
href={`/admin/rounds/${project.round.id}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{project.round.name}
|
||||
</Link>
|
||||
<div className="flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
|
||||
{project.roundProjects?.length > 0 ? (
|
||||
project.roundProjects.map((rp, i) => (
|
||||
<span key={rp.round.id} className="flex items-center gap-1">
|
||||
{i > 0 && <span className="text-muted-foreground/50">/</span>}
|
||||
<Link
|
||||
href={`/admin/rounds/${rp.round.id}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{rp.round.name}
|
||||
</Link>
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span>No round</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
{project.title}
|
||||
</h1>
|
||||
<Badge variant={statusColors[project.status] || 'secondary'}>
|
||||
{project.status.replace('_', ' ')}
|
||||
<Badge variant={statusColors[project.roundProjects?.[0]?.status ?? 'SUBMITTED'] || 'secondary'}>
|
||||
{(project.roundProjects?.[0]?.status ?? 'SUBMITTED').replace('_', ' ')}
|
||||
</Badge>
|
||||
</div>
|
||||
{project.teamName && (
|
||||
@@ -504,7 +513,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href={`/admin/rounds/${project.roundId}/assignments`}>
|
||||
<Link href={`/admin/rounds/${project.roundProjects?.[0]?.round?.id}/assignments`}>
|
||||
Manage
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user