Observer dashboard extraction, PDF reports, jury UX overhaul, and miscellaneous improvements

- Extract observer dashboard to client component, add PDF export button
- Add PDF report generator with jsPDF for analytics reports
- Overhaul jury evaluation page with improved layout and UX
- Add new analytics endpoints for observer/admin reports
- Improve round creation/edit forms with better settings
- Fix filtering rules page, CSV export dialog, notification bell
- Update auth, prisma schema, and various type fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 23:08:00 +01:00
parent 5c8d22ac11
commit d787a24921
31 changed files with 2565 additions and 930 deletions

View File

@@ -37,12 +37,10 @@ import {
Users,
ClipboardList,
CheckCircle2,
PieChart,
TrendingUp,
GitCompare,
UserCheck,
Globe,
Printer,
} from 'lucide-react'
import { formatDateOnly } from '@/lib/utils'
import {
@@ -57,6 +55,7 @@ import {
JurorConsistencyChart,
DiversityMetricsChart,
} from '@/components/charts'
import { ExportPdfButton } from '@/components/shared/export-pdf-button'
function ReportsOverview() {
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeRounds: true })
@@ -631,6 +630,19 @@ function DiversityTab() {
}
export default function ReportsPage() {
const [pdfRoundId, setPdfRoundId] = useState<string | null>(null)
const { data: pdfPrograms } = trpc.program.list.useQuery({ includeRounds: true })
const pdfRounds = pdfPrograms?.flatMap((p) =>
p.rounds.map((r) => ({ id: r.id, name: r.name, programName: `${p.year} Edition` }))
) || []
if (pdfRounds.length && !pdfRoundId) {
setPdfRoundId(pdfRounds[0].id)
}
const selectedPdfRound = pdfRounds.find((r) => r.id === pdfRoundId)
return (
<div className="space-y-6">
{/* Header */}
@@ -666,16 +678,27 @@ export default function ReportsPage() {
Diversity
</TabsTrigger>
</TabsList>
<Button
variant="outline"
size="sm"
onClick={() => {
window.print()
}}
>
<Printer className="mr-2 h-4 w-4" />
Export PDF
</Button>
<div className="flex items-center gap-2">
<Select value={pdfRoundId || ''} onValueChange={setPdfRoundId}>
<SelectTrigger className="w-[220px]">
<SelectValue placeholder="Select round for PDF" />
</SelectTrigger>
<SelectContent>
{pdfRounds.map((round) => (
<SelectItem key={round.id} value={round.id}>
{round.programName} - {round.name}
</SelectItem>
))}
</SelectContent>
</Select>
{pdfRoundId && (
<ExportPdfButton
roundId={pdfRoundId}
roundName={selectedPdfRound?.name}
programName={selectedPdfRound?.programName}
/>
)}
</div>
</div>
<TabsContent value="overview">