Add project reports section and fix mobile overflow issues
Build and Push Docker Image / build (push) Successful in 11m20s Details

- Add project-wide reporting table with scope selector (all rounds / per round)
- Fix horizontal overflow on mobile (body, admin sidebar, logo truncation)
- Make members header and reports tabs responsive with flex-wrap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-11 19:08:04 +01:00
parent bd9cd310fc
commit b5d90d3c26
5 changed files with 116 additions and 18 deletions

View File

@ -65,6 +65,22 @@ function ReportsOverview() {
// Flatten rounds from all programs
const rounds = programs?.flatMap(p => p.rounds.map(r => ({ ...r, programId: p.id, programName: `${p.year} Edition` }))) || []
// Project reporting scope (default: latest program, all rounds)
const [selectedValue, setSelectedValue] = useState<string | null>(null)
if (programs?.length && !selectedValue) {
setSelectedValue(`all:${programs[0].id}`)
}
const scopeInput = parseSelection(selectedValue)
const hasScope = !!scopeInput.roundId || !!scopeInput.programId
const { data: projectRankings, isLoading: projectsLoading } =
trpc.analytics.getProjectRankings.useQuery(
{ ...scopeInput, limit: 5000 },
{ enabled: hasScope }
)
if (isLoading || statsLoading) {
return (
<div className="space-y-6">
@ -203,17 +219,101 @@ function ReportsOverview() {
</Card>
)}
{/* Rounds Table */}
{/* Project Reports (default: all projects, filterable by round) */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="rounded-lg bg-emerald-500/10 p-1.5">
<FileSpreadsheet className="h-4 w-4 text-emerald-600" />
<ClipboardList className="h-4 w-4 text-emerald-600" />
</div>
Round Reports
Project Reports
</CardTitle>
<CardDescription>
View progress and export data for each round
Project-wide reporting across all projects optionally filter to a specific round
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Scope selector */}
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
<span className="text-sm font-medium">Scope:</span>
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
<SelectTrigger className="w-full sm:w-[360px]">
<SelectValue placeholder="All projects" />
</SelectTrigger>
<SelectContent>
{programs?.map((p) => (
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
{p.year} Edition All Rounds
</SelectItem>
))}
{rounds.map((round) => (
<SelectItem key={round.id} value={round.id}>
{round.programName} - {round.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{projectsLoading ? (
<Skeleton className="h-[400px]" />
) : projectRankings?.length ? (
<div className="rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead className="hidden sm:table-cell">Team</TableHead>
<TableHead className="text-right">Avg</TableHead>
<TableHead className="text-right">Evals</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{projectRankings.map((p) => (
<TableRow key={p.id}>
<TableCell className="font-medium">
<Link href={`/admin/projects/${p.id}`} className="hover:underline">
{p.title}
</Link>
</TableCell>
<TableCell className="hidden sm:table-cell text-muted-foreground">
{p.teamName || '-'}
</TableCell>
<TableCell className="text-right tabular-nums">
{p.averageScore === null ? '-' : p.averageScore.toFixed(2)}
</TableCell>
<TableCell className="text-right tabular-nums">{p.evaluationCount}</TableCell>
<TableCell>
<Badge variant="outline">{p.status}</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
) : (
<div className="flex flex-col items-center justify-center py-10 text-center">
<ClipboardList className="h-10 w-10 text-muted-foreground/50" />
<p className="mt-2 text-sm text-muted-foreground">
No project report data available for the selected scope yet.
</p>
</div>
)}
</CardContent>
</Card>
{/* Round exports (still available) */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="rounded-lg bg-blue-500/10 p-1.5">
<FileSpreadsheet className="h-4 w-4 text-blue-600" />
</div>
Round Exports
</CardTitle>
<CardDescription>
Download round-level evaluations and results
</CardDescription>
</CardHeader>
<CardContent>
@ -221,7 +321,7 @@ function ReportsOverview() {
<div className="flex flex-col items-center justify-center py-8 text-center">
<FileSpreadsheet className="h-10 w-10 text-muted-foreground/50" />
<p className="mt-2 text-sm text-muted-foreground">
No rounds created yet. Round-specific reports will appear here once rounds are set up.
No rounds created yet.
</p>
</div>
) : (
@ -230,7 +330,6 @@ function ReportsOverview() {
<TableRow>
<TableHead>Round</TableHead>
<TableHead>Program</TableHead>
<TableHead>Projects</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Export</TableHead>
</TableRow>
@ -249,7 +348,6 @@ function ReportsOverview() {
</div>
</TableCell>
<TableCell>{round.programName}</TableCell>
<TableCell>-</TableCell>
<TableCell>
<Badge
variant={
@ -264,7 +362,7 @@ function ReportsOverview() {
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<div className="flex justify-end gap-2 flex-wrap">
<Button variant="outline" size="sm" asChild>
<a
href={`/api/export/evaluations?roundId=${round.id}`}
@ -754,7 +852,7 @@ export default function ReportsPage() {
{/* Tabs */}
<Tabs defaultValue="overview" className="space-y-6">
<div className="flex items-center justify-between flex-wrap gap-4">
<TabsList>
<TabsList className="w-full sm:w-auto flex-wrap justify-start overflow-x-auto scrollbar-hide">
<TabsTrigger value="overview" className="gap-2">
<FileSpreadsheet className="h-4 w-4" />
Overview
@ -776,7 +874,7 @@ export default function ReportsPage() {
Diversity
</TabsTrigger>
</TabsList>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 w-full sm:w-auto justify-between sm:justify-end">
<Select value={pdfRoundId || ''} onValueChange={setPdfRoundId}>
<SelectTrigger className="w-[220px]">
<SelectValue placeholder="Select round for PDF" />

View File

@ -217,7 +217,7 @@
}
body {
@apply bg-background text-foreground;
@apply bg-background text-foreground overflow-x-hidden;
font-feature-settings: "rlig" 1, "calt" 1;
}

View File

@ -148,14 +148,14 @@ export function MembersContent() {
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<div className="flex items-center justify-between flex-wrap gap-4">
<div className="min-w-0">
<h1 className="text-2xl font-semibold tracking-tight">Members</h1>
<p className="text-muted-foreground">
Manage jury members, mentors, observers, and admins
</p>
</div>
<Button asChild>
<Button asChild className="w-full sm:w-auto shrink-0">
<Link href="/admin/members/invite">
<Plus className="mr-2 h-4 w-4" />
Add Member

View File

@ -167,8 +167,8 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
return (
<>
{/* Mobile menu button */}
<div className="fixed top-0 left-0 right-0 z-40 flex h-16 items-center justify-between border-b bg-card px-4 lg:hidden">
<Logo showText textSuffix="Admin" />
<div className="fixed top-0 left-0 right-0 z-40 flex h-16 items-center justify-between border-b bg-card px-4 lg:hidden overflow-x-hidden">
<Logo showText textSuffix="Admin" className="min-w-0" />
<div className="flex items-center gap-2">
<NotificationBell />
<Button

View File

@ -43,10 +43,10 @@ export function Logo({
priority
/>
{showText && (
<div className="flex items-center gap-1">
<div className="flex items-center gap-1 min-w-0">
<span className="font-semibold">MOPC</span>
{textSuffix && (
<span className="text-xs text-muted-foreground">{textSuffix}</span>
<span className="text-xs text-muted-foreground truncate">{textSuffix}</span>
)}
</div>
)}