Round system redesign: Phases 1-7 complete
Full pipeline/track/stage architecture replacing the legacy round system. Schema: 11 new models (Pipeline, Track, Stage, StageTransition, ProjectStageState, RoutingRule, Cohort, CohortProject, LiveProgressCursor, OverrideAction, AudienceVoter) + 8 new enums. Backend: 9 new routers (pipeline, stage, routing, stageFiltering, stageAssignment, cohort, live, decision, award) + 6 new services (stage-engine, routing-engine, stage-filtering, stage-assignment, stage-notifications, live-control). Frontend: Pipeline wizard (17 components), jury stage pages (7), applicant pipeline pages (3), public stage pages (2), admin pipeline pages (5), shared stage components (3), SSE route, live hook. Phase 6 refit: 23 routers/services migrated from roundId to stageId, all frontend components refitted. Deleted round.ts (985 lines), roundTemplate.ts, round-helpers.ts, round-settings.ts, round-type-settings.tsx, 10 legacy admin pages, 7 legacy jury pages, 3 legacy dialogs. Phase 7 validation: 36 tests (10 unit + 8 integration files) all passing, TypeScript 0 errors, Next.js build succeeds, 13 integrity checks, legacy symbol sweep clean, auto-seed on first Docker startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ export default function ProjectPoolPage() {
|
||||
const [selectedProgramId, setSelectedProgramId] = useState<string>('')
|
||||
const [selectedProjects, setSelectedProjects] = useState<string[]>([])
|
||||
const [assignDialogOpen, setAssignDialogOpen] = useState(false)
|
||||
const [targetRoundId, setTargetRoundId] = useState<string>('')
|
||||
const [targetStageId, setTargetStageId] = useState<string>('')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [categoryFilter, setCategoryFilter] = useState<'STARTUP' | 'BUSINESS_CONCEPT' | 'all'>('all')
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
@@ -50,20 +50,22 @@ export default function ProjectPoolPage() {
|
||||
{ enabled: !!selectedProgramId }
|
||||
)
|
||||
|
||||
const { data: rounds, isLoading: isLoadingRounds } = trpc.round.listByProgram.useQuery(
|
||||
{ programId: selectedProgramId },
|
||||
// Get stages from the selected program (program.list includes rounds/stages)
|
||||
const { data: selectedProgramData, isLoading: isLoadingStages } = trpc.program.get.useQuery(
|
||||
{ id: selectedProgramId },
|
||||
{ enabled: !!selectedProgramId }
|
||||
)
|
||||
const stages = (selectedProgramData?.stages || []) as Array<{ id: string; name: string }>
|
||||
|
||||
const utils = trpc.useUtils()
|
||||
const assignMutation = trpc.projectPool.assignToRound.useMutation({
|
||||
const assignMutation = trpc.projectPool.assignToStage.useMutation({
|
||||
onSuccess: (result) => {
|
||||
utils.project.list.invalidate()
|
||||
utils.round.get.invalidate()
|
||||
toast.success(`Assigned ${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} to round`)
|
||||
utils.program.get.invalidate()
|
||||
toast.success(`Assigned ${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} to stage`)
|
||||
setSelectedProjects([])
|
||||
setAssignDialogOpen(false)
|
||||
setTargetRoundId('')
|
||||
setTargetStageId('')
|
||||
refetch()
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -72,17 +74,17 @@ export default function ProjectPoolPage() {
|
||||
})
|
||||
|
||||
const handleBulkAssign = () => {
|
||||
if (selectedProjects.length === 0 || !targetRoundId) return
|
||||
if (selectedProjects.length === 0 || !targetStageId) return
|
||||
assignMutation.mutate({
|
||||
projectIds: selectedProjects,
|
||||
roundId: targetRoundId,
|
||||
stageId: targetStageId,
|
||||
})
|
||||
}
|
||||
|
||||
const handleQuickAssign = (projectId: string, roundId: string) => {
|
||||
const handleQuickAssign = (projectId: string, stageId: string) => {
|
||||
assignMutation.mutate({
|
||||
projectIds: [projectId],
|
||||
roundId,
|
||||
stageId,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,7 +111,7 @@ export default function ProjectPoolPage() {
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Project Pool</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Assign unassigned projects to evaluation rounds
|
||||
Assign unassigned projects to evaluation stages
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -236,20 +238,20 @@ export default function ProjectPoolPage() {
|
||||
: '-'}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{isLoadingRounds ? (
|
||||
{isLoadingStages ? (
|
||||
<Skeleton className="h-9 w-[200px]" />
|
||||
) : (
|
||||
<Select
|
||||
onValueChange={(roundId) => handleQuickAssign(project.id, roundId)}
|
||||
onValueChange={(stageId) => handleQuickAssign(project.id, stageId)}
|
||||
disabled={assignMutation.isPending}
|
||||
>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue placeholder="Assign to round..." />
|
||||
<SelectValue placeholder="Assign to stage..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{rounds?.map((round: { id: string; name: string; sortOrder: number }) => (
|
||||
<SelectItem key={round.id} value={round.id}>
|
||||
{round.name}
|
||||
{stages?.map((stage: { id: string; name: string }) => (
|
||||
<SelectItem key={stage.id} value={stage.id}>
|
||||
{stage.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -310,20 +312,20 @@ export default function ProjectPoolPage() {
|
||||
<Dialog open={assignDialogOpen} onOpenChange={setAssignDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Assign Projects to Round</DialogTitle>
|
||||
<DialogTitle>Assign Projects to Stage</DialogTitle>
|
||||
<DialogDescription>
|
||||
Assign {selectedProjects.length} selected project{selectedProjects.length > 1 ? 's' : ''} to:
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Select value={targetRoundId} onValueChange={setTargetRoundId}>
|
||||
<Select value={targetStageId} onValueChange={setTargetStageId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select round..." />
|
||||
<SelectValue placeholder="Select stage..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{rounds?.map((round: { id: string; name: string; sortOrder: number }) => (
|
||||
<SelectItem key={round.id} value={round.id}>
|
||||
{round.name}
|
||||
{stages?.map((stage: { id: string; name: string }) => (
|
||||
<SelectItem key={stage.id} value={stage.id}>
|
||||
{stage.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -335,7 +337,7 @@ export default function ProjectPoolPage() {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleBulkAssign}
|
||||
disabled={!targetRoundId || assignMutation.isPending}
|
||||
disabled={!targetStageId || assignMutation.isPending}
|
||||
>
|
||||
{assignMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Assign
|
||||
|
||||
Reference in New Issue
Block a user