feat: group observer project files by round
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m15s

Files on the observer project detail page are now grouped by round
(e.g., "Application Intake", "Semi-Finals Document Submission") instead
of shown in a flat list. Uses FileViewer's existing groupedFiles prop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 16:24:33 +01:00
parent 0390d05727
commit a8b8643936
2 changed files with 64 additions and 35 deletions

View File

@@ -929,41 +929,66 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{project.files && project.files.length > 0 ? ( {project.files && project.files.length > 0 ? (() => {
<FileViewer // Group files by round
projectId={projectId} type FileItem = (typeof project.files)[number]
files={project.files.map((f) => ({ const roundMap = new Map<string, { roundId: string | null; roundName: string; sortOrder: number; files: FileItem[] }>()
id: f.id, for (const f of project.files) {
fileName: f.fileName, const key = (f as any).roundId ?? '__none__'
fileType: f.fileType as if (!roundMap.has(key)) {
| 'EXEC_SUMMARY' const round = (f as any).round as { id: string; name: string; sortOrder: number } | null
| 'PRESENTATION' roundMap.set(key, {
| 'VIDEO' roundId: round?.id ?? null,
| 'OTHER' roundName: round?.name ?? 'Other Files',
| 'BUSINESS_PLAN' sortOrder: round?.sortOrder ?? 999,
| 'VIDEO_PITCH' files: [],
| 'SUPPORTING_DOC', })
mimeType: f.mimeType, }
size: f.size, roundMap.get(key)!.files.push(f)
bucket: f.bucket, }
objectKey: f.objectKey, const groups = Array.from(roundMap.values()).sort((a, b) => a.sortOrder - b.sortOrder)
pageCount: f.pageCount,
textPreview: f.textPreview, return (
detectedLang: f.detectedLang, <FileViewer
langConfidence: f.langConfidence, projectId={projectId}
analyzedAt: f.analyzedAt ? String(f.analyzedAt) : null, groupedFiles={groups.map((g) => ({
requirementId: f.requirementId, roundId: g.roundId,
requirement: f.requirement roundName: g.roundName,
? { sortOrder: g.sortOrder,
id: f.requirement.id, files: g.files.map((f) => ({
name: f.requirement.name, id: f.id,
description: f.requirement.description, fileName: f.fileName,
isRequired: f.requirement.isRequired, fileType: f.fileType as
} | 'EXEC_SUMMARY'
: null, | 'PRESENTATION'
}))} | 'VIDEO'
/> | 'OTHER'
) : ( | 'BUSINESS_PLAN'
| 'VIDEO_PITCH'
| 'SUPPORTING_DOC',
mimeType: f.mimeType,
size: f.size,
bucket: f.bucket,
objectKey: f.objectKey,
pageCount: f.pageCount,
textPreview: f.textPreview,
detectedLang: f.detectedLang,
langConfidence: f.langConfidence,
analyzedAt: f.analyzedAt ? String(f.analyzedAt) : null,
requirementId: f.requirementId,
requirement: f.requirement
? {
id: f.requirement.id,
name: f.requirement.name,
description: f.requirement.description,
isRequired: f.requirement.isRequired,
}
: null,
})),
}))}
/>
)
})() : (
<div className="flex flex-col items-center justify-center py-8 text-center"> <div className="flex flex-col items-center justify-center py-8 text-center">
<FileText className="h-12 w-12 text-muted-foreground/50" /> <FileText className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">

View File

@@ -1378,9 +1378,12 @@ export const analyticsRouter = router({
id: true, fileName: true, fileType: true, mimeType: true, size: true, id: true, fileName: true, fileType: true, mimeType: true, size: true,
bucket: true, objectKey: true, pageCount: true, textPreview: true, bucket: true, objectKey: true, pageCount: true, textPreview: true,
detectedLang: true, langConfidence: true, analyzedAt: true, detectedLang: true, langConfidence: true, analyzedAt: true,
roundId: true,
round: { select: { id: true, name: true, roundType: true, sortOrder: true } },
requirementId: true, requirementId: true,
requirement: { select: { id: true, name: true, description: true, isRequired: true } }, requirement: { select: { id: true, name: true, description: true, isRequired: true } },
}, },
orderBy: [{ round: { sortOrder: 'asc' } }, { createdAt: 'asc' }],
}, },
teamMembers: { teamMembers: {
include: { include: {
@@ -1526,6 +1529,7 @@ export const analyticsRouter = router({
return { return {
project: { project: {
...projectRaw, ...projectRaw,
files: projectRaw.files,
projectTags, projectTags,
teamMembers: teamMembersWithAvatars, teamMembers: teamMembersWithAvatars,
}, },