'use client' import { useState } from 'react' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { FileText, Download, ExternalLink, Loader2 } from 'lucide-react' import { toast } from 'sonner' interface MultiWindowDocViewerProps { roundId: string projectId: 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] } function getFileIcon(mimeType: string) { if (mimeType.startsWith('image/')) return '🖼️' if (mimeType.startsWith('video/')) return '🎥' if (mimeType.includes('pdf')) return '📄' if (mimeType.includes('word') || mimeType.includes('document')) return '📝' if (mimeType.includes('sheet') || mimeType.includes('excel')) return '📊' if (mimeType.includes('presentation') || mimeType.includes('powerpoint')) return '📊' return '📎' } function canPreviewInBrowser(mimeType: string): boolean { return ( mimeType === 'application/pdf' || mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('text/') ) } function FileCard({ file }: { file: { id: string; fileName: string; mimeType: string; size: number; bucket: string; objectKey: string } }) { const [loadingAction, setLoadingAction] = useState<'download' | 'preview' | null>(null) const downloadUrlQuery = trpc.file.getDownloadUrl.useQuery( { bucket: file.bucket, objectKey: file.objectKey }, { enabled: false } // manual trigger ) const handleAction = async (action: 'download' | 'preview') => { setLoadingAction(action) try { const result = await downloadUrlQuery.refetch() if (result.data?.url) { if (action === 'preview' && canPreviewInBrowser(file.mimeType)) { window.open(result.data.url, '_blank') } else { // Download: create temp link and click const a = document.createElement('a') a.href = result.data.url a.download = file.fileName a.target = '_blank' document.body.appendChild(a) a.click() document.body.removeChild(a) } } } catch { toast.error('Failed to get file URL') } finally { setLoadingAction(null) } } return (
{getFileIcon(file.mimeType || '')}

{file.fileName}

{file.mimeType?.split('/')[1]?.toUpperCase() || 'FILE'} {file.size > 0 && ( {formatFileSize(file.size)} )}
{canPreviewInBrowser(file.mimeType) && ( )}
) } export function MultiWindowDocViewer({ roundId, projectId }: MultiWindowDocViewerProps) { const { data: files, isLoading } = trpc.file.listByProject.useQuery( { projectId }, { enabled: !!projectId } ) if (isLoading) { return ( ) } if (!files || files.length === 0) { return ( Documents Project files and submissions

No files uploaded

) } // Group files by round name or "General" const grouped: Record = {} for (const file of files) { const groupName = file.requirement?.round?.name ?? 'General' if (!grouped[groupName]) grouped[groupName] = [] grouped[groupName].push(file) } const groupNames = Object.keys(grouped) return ( Documents {files.length} file{files.length !== 1 ? 's' : ''} submitted {groupNames.length === 1 ? ( // Single group — no need for headers
{grouped[groupNames[0]].map((file) => ( ))}
) : ( // Multiple groups — show headers
{groupNames.map((groupName) => (

{groupName} {grouped[groupName].length}

{grouped[groupName].map((file) => ( ))}
))}
)}
) }