Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
import { trpc } from '@/lib/trpc/client'
|
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select'
|
|
|
|
|
import { FileText, Upload, CheckCircle2, ArrowUp } from 'lucide-react'
|
|
|
|
|
import { toast } from 'sonner'
|
|
|
|
|
|
|
|
|
|
interface FilePromotionPanelProps {
|
|
|
|
|
mentorAssignmentId: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatFileSize(bytes: number): string {
|
|
|
|
|
if (bytes === 0) return '0 B'
|
|
|
|
|
const k = 1024
|
|
|
|
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function FilePromotionPanel({ mentorAssignmentId }: FilePromotionPanelProps) {
|
|
|
|
|
const [selectedSlot, setSelectedSlot] = useState<string>('')
|
|
|
|
|
const utils = trpc.useUtils()
|
|
|
|
|
|
2026-04-28 13:33:18 +02:00
|
|
|
const { data: workspaceFiles = [], isLoading: filesLoading } =
|
|
|
|
|
trpc.mentor.workspaceGetFiles.useQuery(
|
|
|
|
|
{ mentorAssignmentId },
|
|
|
|
|
{ enabled: !!mentorAssignmentId },
|
|
|
|
|
)
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
|
|
|
|
|
|
|
|
const promoteMutation = trpc.mentor.workspacePromoteFile.useMutation({
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
toast.success('File promoted successfully')
|
|
|
|
|
setSelectedSlot('')
|
|
|
|
|
},
|
|
|
|
|
onError: (err) => toast.error(err.message),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handlePromote = (mentorFileId: string) => {
|
|
|
|
|
if (!selectedSlot) {
|
|
|
|
|
toast.error('Please select a file requirement slot')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
promoteMutation.mutate({
|
|
|
|
|
roundId: '', // Would need to get this from context
|
|
|
|
|
mentorFileId,
|
|
|
|
|
slotKey: selectedSlot,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 13:33:18 +02:00
|
|
|
const isLoading = filesLoading
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<Skeleton className="h-6 w-48" />
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{[1, 2, 3].map((i) => (
|
|
|
|
|
<Skeleton key={i} className="h-20" />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2">
|
|
|
|
|
<Upload className="h-5 w-5" />
|
|
|
|
|
File Promotion
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Promote workspace files to official submission windows
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
{/* Slot selector */}
|
|
|
|
|
<div className="mb-6">
|
|
|
|
|
<label className="text-sm font-medium mb-2 block">
|
|
|
|
|
Target File Requirement
|
|
|
|
|
</label>
|
|
|
|
|
<Select value={selectedSlot} onValueChange={setSelectedSlot}>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Select a file requirement..." />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{/* Mock slots - in real implementation, would fetch available requirements */}
|
|
|
|
|
<SelectItem value="pitch_deck">Pitch Deck</SelectItem>
|
|
|
|
|
<SelectItem value="business_plan">Business Plan</SelectItem>
|
|
|
|
|
<SelectItem value="presentation">Presentation</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
|
|
|
Select which file requirement to promote files to
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Workspace files list */}
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{workspaceFiles.length === 0 ? (
|
|
|
|
|
<div className="text-center py-12 border border-dashed rounded-lg">
|
|
|
|
|
<FileText className="h-12 w-12 text-muted-foreground/50 mx-auto mb-3" />
|
|
|
|
|
<p className="text-sm text-muted-foreground">No workspace files available</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
|
|
|
Files shared in the workspace will appear here
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
workspaceFiles.map((file: any) => {
|
|
|
|
|
const isPromoted = file.promotedToWindow
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card key={file.id}>
|
|
|
|
|
<CardContent className="p-4">
|
|
|
|
|
<div className="flex items-start gap-4">
|
|
|
|
|
<div className="rounded-lg bg-brand-blue/10 p-3 shrink-0">
|
|
|
|
|
<FileText className="h-5 w-5 text-brand-blue" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-start justify-between gap-2 mb-2">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p className="font-medium text-sm truncate" title={file.filename}>
|
|
|
|
|
{file.filename}
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex items-center gap-2 mt-1">
|
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
|
{file.mimeType?.split('/')[1]?.toUpperCase() || 'FILE'}
|
|
|
|
|
</Badge>
|
|
|
|
|
{file.size && (
|
|
|
|
|
<span className="text-xs text-muted-foreground">
|
|
|
|
|
{formatFileSize(file.size)}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{isPromoted ? (
|
|
|
|
|
<Badge variant="default" className="shrink-0 bg-emerald-50 text-emerald-700 border-emerald-200">
|
|
|
|
|
<CheckCircle2 className="mr-1 h-3 w-3" />
|
|
|
|
|
Promoted
|
|
|
|
|
</Badge>
|
|
|
|
|
) : (
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => handlePromote(file.id)}
|
|
|
|
|
disabled={!selectedSlot || promoteMutation.isPending}
|
|
|
|
|
className="shrink-0"
|
|
|
|
|
>
|
|
|
|
|
<ArrowUp className="mr-1 h-3 w-3" />
|
|
|
|
|
Promote
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{isPromoted && file.promotedToWindow && (
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Promoted to: {file.promotedToWindow.name}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{workspaceFiles.length > 0 && (
|
|
|
|
|
<div className="mt-4 p-3 bg-muted/50 rounded-lg">
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
<strong>Note:</strong> Promoting a file will make it visible to jurors in the selected
|
|
|
|
|
submission window. This action can help teams submit refined versions of their work.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|