2026-02-08 23:01:33 +01:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
import { useState, useCallback, useRef } from 'react'
|
|
|
|
|
import { trpc } from '@/lib/trpc/client'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
|
import { Progress } from '@/components/ui/progress'
|
|
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import {
|
|
|
|
|
Upload,
|
|
|
|
|
FileIcon,
|
|
|
|
|
CheckCircle2,
|
|
|
|
|
AlertCircle,
|
|
|
|
|
Loader2,
|
|
|
|
|
Trash2,
|
|
|
|
|
RefreshCw,
|
|
|
|
|
} from 'lucide-react'
|
|
|
|
|
import { cn, formatFileSize } from '@/lib/utils'
|
|
|
|
|
import { toast } from 'sonner'
|
|
|
|
|
|
|
|
|
|
function getMimeLabel(mime: string): string {
|
|
|
|
|
if (mime === 'application/pdf') return 'PDF'
|
|
|
|
|
if (mime.startsWith('image/')) return 'Images'
|
|
|
|
|
if (mime.startsWith('video/')) return 'Video'
|
|
|
|
|
if (mime.includes('wordprocessingml')) return 'Word'
|
|
|
|
|
if (mime.includes('spreadsheetml')) return 'Excel'
|
|
|
|
|
if (mime.includes('presentationml')) return 'PowerPoint'
|
|
|
|
|
if (mime.endsWith('/*')) return mime.replace('/*', '')
|
|
|
|
|
return mime
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface FileRequirement {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
|
|
|
|
description?: string | null
|
|
|
|
|
acceptedMimeTypes: string[]
|
|
|
|
|
maxSizeMB?: number | null
|
|
|
|
|
isRequired: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface UploadedFile {
|
|
|
|
|
id: string
|
|
|
|
|
fileName: string
|
|
|
|
|
mimeType: string
|
|
|
|
|
size: number
|
|
|
|
|
createdAt: string | Date
|
|
|
|
|
requirementId?: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface RequirementUploadSlotProps {
|
|
|
|
|
requirement: FileRequirement
|
|
|
|
|
existingFile?: UploadedFile | null
|
|
|
|
|
projectId: string
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId: string
|
2026-02-08 23:01:33 +01:00
|
|
|
onFileChange?: () => void
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function RequirementUploadSlot({
|
|
|
|
|
requirement,
|
|
|
|
|
existingFile,
|
|
|
|
|
projectId,
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId,
|
2026-02-08 23:01:33 +01:00
|
|
|
onFileChange,
|
|
|
|
|
disabled = false,
|
|
|
|
|
}: RequirementUploadSlotProps) {
|
|
|
|
|
const [uploading, setUploading] = useState(false)
|
|
|
|
|
const [progress, setProgress] = useState(0)
|
|
|
|
|
const [deleting, setDeleting] = useState(false)
|
|
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
|
|
|
|
const getUploadUrl = trpc.applicant.getUploadUrl.useMutation()
|
|
|
|
|
const saveFileMetadata = trpc.applicant.saveFileMetadata.useMutation()
|
|
|
|
|
const deleteFile = trpc.applicant.deleteFile.useMutation()
|
|
|
|
|
|
|
|
|
|
const acceptsMime = useCallback(
|
|
|
|
|
(mimeType: string) => {
|
|
|
|
|
if (requirement.acceptedMimeTypes.length === 0) return true
|
|
|
|
|
return requirement.acceptedMimeTypes.some((pattern) => {
|
|
|
|
|
if (pattern.endsWith('/*')) {
|
|
|
|
|
return mimeType.startsWith(pattern.replace('/*', '/'))
|
|
|
|
|
}
|
|
|
|
|
return mimeType === pattern
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
[requirement.acceptedMimeTypes]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const handleFileSelect = useCallback(
|
|
|
|
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = e.target.files?.[0]
|
|
|
|
|
if (!file) return
|
|
|
|
|
|
|
|
|
|
// Reset input
|
|
|
|
|
if (fileInputRef.current) fileInputRef.current.value = ''
|
|
|
|
|
|
|
|
|
|
// Validate mime type
|
|
|
|
|
if (!acceptsMime(file.type)) {
|
|
|
|
|
toast.error(`File type ${file.type} is not accepted for this requirement`)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate size
|
|
|
|
|
if (requirement.maxSizeMB && file.size > requirement.maxSizeMB * 1024 * 1024) {
|
|
|
|
|
toast.error(`File exceeds maximum size of ${requirement.maxSizeMB}MB`)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setUploading(true)
|
|
|
|
|
setProgress(0)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Get presigned URL
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
const { url, bucket, objectKey, isLate, stageId: uploadStageId } =
|
2026-02-08 23:01:33 +01:00
|
|
|
await getUploadUrl.mutateAsync({
|
|
|
|
|
projectId,
|
|
|
|
|
fileName: file.name,
|
|
|
|
|
mimeType: file.type,
|
|
|
|
|
fileType: 'OTHER',
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId,
|
2026-02-08 23:01:33 +01:00
|
|
|
requirementId: requirement.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Upload file with progress tracking
|
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
|
|
|
const xhr = new XMLHttpRequest()
|
|
|
|
|
xhr.upload.addEventListener('progress', (event) => {
|
|
|
|
|
if (event.lengthComputable) {
|
|
|
|
|
setProgress(Math.round((event.loaded / event.total) * 100))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
xhr.addEventListener('load', () => {
|
|
|
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
|
|
|
resolve()
|
|
|
|
|
} else {
|
|
|
|
|
reject(new Error(`Upload failed with status ${xhr.status}`))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
xhr.addEventListener('error', () => reject(new Error('Upload failed')))
|
|
|
|
|
xhr.open('PUT', url)
|
|
|
|
|
xhr.setRequestHeader('Content-Type', file.type)
|
|
|
|
|
xhr.send(file)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Save metadata
|
|
|
|
|
await saveFileMetadata.mutateAsync({
|
|
|
|
|
projectId,
|
|
|
|
|
fileName: file.name,
|
|
|
|
|
mimeType: file.type,
|
|
|
|
|
size: file.size,
|
|
|
|
|
fileType: 'OTHER',
|
|
|
|
|
bucket,
|
|
|
|
|
objectKey,
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId: uploadStageId || stageId,
|
2026-02-08 23:01:33 +01:00
|
|
|
isLate: isLate || false,
|
|
|
|
|
requirementId: requirement.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
toast.success(`${requirement.name} uploaded successfully`)
|
|
|
|
|
onFileChange?.()
|
|
|
|
|
} catch (err) {
|
|
|
|
|
toast.error(err instanceof Error ? err.message : 'Upload failed')
|
|
|
|
|
} finally {
|
|
|
|
|
setUploading(false)
|
|
|
|
|
setProgress(0)
|
|
|
|
|
}
|
|
|
|
|
},
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
[projectId, stageId, requirement, acceptsMime, getUploadUrl, saveFileMetadata, onFileChange]
|
2026-02-08 23:01:33 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const handleDelete = useCallback(async () => {
|
|
|
|
|
if (!existingFile) return
|
|
|
|
|
setDeleting(true)
|
|
|
|
|
try {
|
|
|
|
|
await deleteFile.mutateAsync({ fileId: existingFile.id })
|
|
|
|
|
toast.success('File deleted')
|
|
|
|
|
onFileChange?.()
|
|
|
|
|
} catch (err) {
|
|
|
|
|
toast.error(err instanceof Error ? err.message : 'Delete failed')
|
|
|
|
|
} finally {
|
|
|
|
|
setDeleting(false)
|
|
|
|
|
}
|
|
|
|
|
}, [existingFile, deleteFile, onFileChange])
|
|
|
|
|
|
|
|
|
|
const isFulfilled = !!existingFile
|
|
|
|
|
const statusColor = isFulfilled
|
|
|
|
|
? 'border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950'
|
|
|
|
|
: requirement.isRequired
|
|
|
|
|
? 'border-red-200 bg-red-50 dark:border-red-900 dark:bg-red-950'
|
|
|
|
|
: 'border-muted'
|
|
|
|
|
|
|
|
|
|
// Build accept string for file input
|
|
|
|
|
const acceptStr =
|
|
|
|
|
requirement.acceptedMimeTypes.length > 0
|
|
|
|
|
? requirement.acceptedMimeTypes.join(',')
|
|
|
|
|
: undefined
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('rounded-lg border p-4 transition-colors', statusColor)}>
|
|
|
|
|
<div className="flex items-start justify-between gap-3">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2 mb-1">
|
|
|
|
|
{isFulfilled ? (
|
|
|
|
|
<CheckCircle2 className="h-4 w-4 text-green-600 shrink-0" />
|
|
|
|
|
) : requirement.isRequired ? (
|
|
|
|
|
<AlertCircle className="h-4 w-4 text-red-500 shrink-0" />
|
|
|
|
|
) : (
|
|
|
|
|
<FileIcon className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
|
|
|
)}
|
|
|
|
|
<span className="font-medium text-sm">{requirement.name}</span>
|
|
|
|
|
<Badge
|
|
|
|
|
variant={requirement.isRequired ? 'destructive' : 'secondary'}
|
|
|
|
|
className="text-xs shrink-0"
|
|
|
|
|
>
|
|
|
|
|
{requirement.isRequired ? 'Required' : 'Optional'}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{requirement.description && (
|
|
|
|
|
<p className="text-xs text-muted-foreground ml-6 mb-2">
|
|
|
|
|
{requirement.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap gap-1 ml-6 mb-2">
|
|
|
|
|
{requirement.acceptedMimeTypes.map((mime) => (
|
|
|
|
|
<Badge key={mime} variant="outline" className="text-xs">
|
|
|
|
|
{getMimeLabel(mime)}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
{requirement.maxSizeMB && (
|
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
|
Max {requirement.maxSizeMB}MB
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{existingFile && (
|
|
|
|
|
<div className="ml-6 flex items-center gap-2 text-xs text-muted-foreground">
|
|
|
|
|
<FileIcon className="h-3 w-3" />
|
|
|
|
|
<span className="truncate">{existingFile.fileName}</span>
|
|
|
|
|
<span>({formatFileSize(existingFile.size)})</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{uploading && (
|
|
|
|
|
<div className="ml-6 mt-2">
|
|
|
|
|
<Progress value={progress} className="h-1.5" />
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1">Uploading... {progress}%</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{!disabled && (
|
|
|
|
|
<div className="flex items-center gap-1 shrink-0">
|
|
|
|
|
{existingFile ? (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
disabled={uploading}
|
|
|
|
|
>
|
|
|
|
|
<RefreshCw className="mr-1 h-3 w-3" />
|
|
|
|
|
Replace
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-8 w-8 text-destructive hover:text-destructive"
|
|
|
|
|
onClick={handleDelete}
|
|
|
|
|
disabled={deleting}
|
|
|
|
|
>
|
|
|
|
|
{deleting ? (
|
|
|
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
disabled={uploading}
|
|
|
|
|
>
|
|
|
|
|
{uploading ? (
|
|
|
|
|
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<Upload className="mr-1 h-3 w-3" />
|
|
|
|
|
)}
|
|
|
|
|
Upload
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
className="hidden"
|
|
|
|
|
accept={acceptStr}
|
|
|
|
|
onChange={handleFileSelect}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface RequirementUploadListProps {
|
|
|
|
|
projectId: string
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId: string
|
2026-02-08 23:01:33 +01:00
|
|
|
disabled?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
export function RequirementUploadList({ projectId, stageId, disabled }: RequirementUploadListProps) {
|
2026-02-08 23:01:33 +01:00
|
|
|
const utils = trpc.useUtils()
|
|
|
|
|
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
const { data: requirements = [] } = trpc.file.listRequirements.useQuery({
|
|
|
|
|
stageId,
|
|
|
|
|
})
|
|
|
|
|
const { data: files = [] } = trpc.file.listByProject.useQuery({ projectId, stageId })
|
2026-02-08 23:01:33 +01:00
|
|
|
|
|
|
|
|
if (requirements.length === 0) return null
|
|
|
|
|
|
|
|
|
|
const handleFileChange = () => {
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
utils.file.listByProject.invalidate({ projectId, stageId })
|
2026-02-08 23:01:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
|
|
|
Required Documents
|
|
|
|
|
</h3>
|
|
|
|
|
{requirements.map((req) => {
|
|
|
|
|
const existing = files.find(
|
|
|
|
|
(f) => (f as { requirementId?: string | null }).requirementId === req.id
|
|
|
|
|
)
|
|
|
|
|
return (
|
|
|
|
|
<RequirementUploadSlot
|
|
|
|
|
key={req.id}
|
|
|
|
|
requirement={req}
|
|
|
|
|
existingFile={
|
|
|
|
|
existing
|
|
|
|
|
? {
|
|
|
|
|
id: existing.id,
|
|
|
|
|
fileName: existing.fileName,
|
|
|
|
|
mimeType: existing.mimeType,
|
|
|
|
|
size: existing.size,
|
|
|
|
|
createdAt: existing.createdAt,
|
|
|
|
|
requirementId: (existing as { requirementId?: string | null }).requirementId,
|
|
|
|
|
}
|
|
|
|
|
: null
|
|
|
|
|
}
|
|
|
|
|
projectId={projectId}
|
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>
2026-02-13 13:57:09 +01:00
|
|
|
stageId={stageId}
|
2026-02-08 23:01:33 +01:00
|
|
|
onFileChange={handleFileChange}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|