feat: lift round selector to reports page top-level
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
Top-level selector in the URL (?round=...) drives every single-round tab (Overview, Analytics, Juror Consistency, Diversity) and narrows the Pipeline tab to the selected program. Cross-Round keeps its own multi-select because it compares multiple rounds by design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -66,27 +67,24 @@ import {
|
|||||||
import { ExportPdfButton } from '@/components/shared/export-pdf-button'
|
import { ExportPdfButton } from '@/components/shared/export-pdf-button'
|
||||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||||
|
|
||||||
function ReportsOverview() {
|
function ReportsOverview({ scope }: { scope: string | null }) {
|
||||||
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
|
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
const { data: dashStats, isLoading: statsLoading } = trpc.analytics.getDashboardStats.useQuery()
|
const { data: dashStats, isLoading: statsLoading } = trpc.analytics.getDashboardStats.useQuery()
|
||||||
|
|
||||||
// Flatten stages from all programs
|
|
||||||
const rounds = programs?.flatMap(p =>
|
const rounds = programs?.flatMap(p =>
|
||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string; votingEndAt?: string | Date | null }>).map((s: { id: string; name: string; status: string; votingEndAt?: string | Date | null }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string; status: string; votingEndAt?: string | Date | null }>).map((s: { id: string; name: string; status: string; votingEndAt?: string | Date | null }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
// Project reporting scope (default: latest program, all rounds)
|
const scopeInput = parseSelection(scope)
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (programs?.length && !selectedValue) {
|
|
||||||
setSelectedValue(`all:${programs[0].id}`)
|
|
||||||
}
|
|
||||||
}, [programs, selectedValue])
|
|
||||||
|
|
||||||
const scopeInput = parseSelection(selectedValue)
|
|
||||||
const hasScope = !!scopeInput.roundId || !!scopeInput.programId
|
const hasScope = !!scopeInput.roundId || !!scopeInput.programId
|
||||||
|
|
||||||
|
const selectedRound = scope ? rounds.find((r) => r.id === scope) : null
|
||||||
|
const selectedScopeLabel = selectedRound
|
||||||
|
? `${selectedRound.programName} — ${selectedRound.name}`
|
||||||
|
: scope?.startsWith('all:')
|
||||||
|
? `${programs?.find((p) => `all:${p.id}` === scope)?.year ?? ''} Edition — All Rounds`
|
||||||
|
: 'All projects'
|
||||||
|
|
||||||
const { data: projectRankings, isLoading: projectsLoading } =
|
const { data: projectRankings, isLoading: projectsLoading } =
|
||||||
trpc.analytics.getProjectRankings.useQuery(
|
trpc.analytics.getProjectRankings.useQuery(
|
||||||
{ ...scopeInput, limit: 5000 },
|
{ ...scopeInput, limit: 5000 },
|
||||||
@@ -243,26 +241,9 @@ function ReportsOverview() {
|
|||||||
Project Reports
|
Project Reports
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Summary dashboard — optionally filter to a specific round
|
{selectedScopeLabel}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
|
||||||
<SelectTrigger className="w-[280px]">
|
|
||||||
<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>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-5">
|
<CardContent className="space-y-5">
|
||||||
@@ -550,9 +531,7 @@ function findDefaultRound(rounds: Array<{ id: string; status?: string }>): strin
|
|||||||
return rounds[0]?.id
|
return rounds[0]?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
function StageAnalytics() {
|
function StageAnalytics({ scope }: { scope: string | null }) {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
// Flatten stages from all programs with program name
|
// Flatten stages from all programs with program name
|
||||||
@@ -560,14 +539,7 @@ function StageAnalytics() {
|
|||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map((s: { id: string; name: string; status: string }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map((s: { id: string; name: string; status: string }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
// Set default selected stage — prefer active round
|
const queryInput = parseSelection(scope)
|
||||||
useEffect(() => {
|
|
||||||
if (rounds.length && !selectedValue) {
|
|
||||||
setSelectedValue(findDefaultRound(rounds) ?? rounds[0].id)
|
|
||||||
}
|
|
||||||
}, [rounds.length, selectedValue])
|
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: scoreDistribution, isLoading: scoreLoading } =
|
const { data: scoreDistribution, isLoading: scoreLoading } =
|
||||||
@@ -606,7 +578,7 @@ function StageAnalytics() {
|
|||||||
{ enabled: hasSelection }
|
{ enabled: hasSelection }
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedRound = rounds.find((r) => r.id === selectedValue)
|
const selectedRound = rounds.find((r) => r.id === scope)
|
||||||
const geoInput = queryInput.programId
|
const geoInput = queryInput.programId
|
||||||
? { programId: queryInput.programId }
|
? { programId: queryInput.programId }
|
||||||
: { programId: selectedRound?.programId || '', roundId: queryInput.roundId }
|
: { programId: selectedRound?.programId || '', roundId: queryInput.roundId }
|
||||||
@@ -644,28 +616,6 @@ function StageAnalytics() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Round Selector */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<label className="text-sm font-medium">Select Round:</label>
|
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
|
||||||
<SelectTrigger className="w-[300px]">
|
|
||||||
<SelectValue placeholder="Select a round" />
|
|
||||||
</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>
|
|
||||||
|
|
||||||
{hasSelection && (
|
{hasSelection && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Row 1: Score Distribution & Status Breakdown */}
|
{/* Row 1: Score Distribution & Status Breakdown */}
|
||||||
@@ -838,22 +788,10 @@ function CrossStageTab() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function JurorConsistencyTab() {
|
function JurorConsistencyTab({ scope }: { scope: string | null }) {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const { isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
const queryInput = parseSelection(scope)
|
||||||
|
|
||||||
const stages = programs?.flatMap((p) =>
|
|
||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map((s: { id: string; name: string; status: string }) => ({ id: s.id, name: s.name, status: s.status, programId: p.id, programName: `${p.year} Edition` }))
|
|
||||||
) || []
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (stages.length && !selectedValue) {
|
|
||||||
setSelectedValue(findDefaultRound(stages) ?? stages[0].id)
|
|
||||||
}
|
|
||||||
}, [stages.length, selectedValue])
|
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: consistency, isLoading: consistencyLoading } =
|
const { data: consistency, isLoading: consistencyLoading } =
|
||||||
@@ -868,27 +806,6 @@ function JurorConsistencyTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<label className="text-sm font-medium">Select Stage:</label>
|
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
|
||||||
<SelectTrigger className="w-[300px]">
|
|
||||||
<SelectValue placeholder="Select a stage" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{programs?.map((p) => (
|
|
||||||
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
|
||||||
{p.year} Edition — All Stages
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
{stages.map((stage) => (
|
|
||||||
<SelectItem key={stage.id} value={stage.id}>
|
|
||||||
{stage.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{consistencyLoading && <Skeleton className="h-[400px]" />}
|
{consistencyLoading && <Skeleton className="h-[400px]" />}
|
||||||
|
|
||||||
{consistency && (
|
{consistency && (
|
||||||
@@ -1052,22 +969,10 @@ function JurorCalibrationPanel({ roundId }: { roundId: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DiversityTab() {
|
function DiversityTab({ scope }: { scope: string | null }) {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const { isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
const queryInput = parseSelection(scope)
|
||||||
|
|
||||||
const stages = programs?.flatMap((p) =>
|
|
||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map((s: { id: string; name: string; status: string }) => ({ id: s.id, name: s.name, status: s.status, programId: p.id, programName: `${p.year} Edition` }))
|
|
||||||
) || []
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (stages.length && !selectedValue) {
|
|
||||||
setSelectedValue(findDefaultRound(stages) ?? stages[0].id)
|
|
||||||
}
|
|
||||||
}, [stages.length, selectedValue])
|
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: diversity, isLoading: diversityLoading } =
|
const { data: diversity, isLoading: diversityLoading } =
|
||||||
@@ -1082,27 +987,6 @@ function DiversityTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<label className="text-sm font-medium">Select Stage:</label>
|
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
|
||||||
<SelectTrigger className="w-[300px]">
|
|
||||||
<SelectValue placeholder="Select a stage" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{programs?.map((p) => (
|
|
||||||
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
|
||||||
{p.year} Edition — All Stages
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
{stages.map((stage) => (
|
|
||||||
<SelectItem key={stage.id} value={stage.id}>
|
|
||||||
{stage.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{diversityLoading && <Skeleton className="h-[400px]" />}
|
{diversityLoading && <Skeleton className="h-[400px]" />}
|
||||||
|
|
||||||
{diversity && (
|
{diversity && (
|
||||||
@@ -1120,10 +1004,10 @@ function DiversityTab() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoundPipelineTab() {
|
function RoundPipelineTab({ scope }: { scope: string | null }) {
|
||||||
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
|
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const rounds = programs?.flatMap(p =>
|
const allRounds = programs?.flatMap(p =>
|
||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string; type?: string }>).map((s) => ({
|
((p.stages ?? []) as Array<{ id: string; name: string; status: string; type?: string }>).map((s) => ({
|
||||||
...s,
|
...s,
|
||||||
programId: p.id,
|
programId: p.id,
|
||||||
@@ -1131,6 +1015,16 @@ function RoundPipelineTab() {
|
|||||||
}))
|
}))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
|
// Pipeline is inherently multi-round. Narrow to the selected program if one
|
||||||
|
// is picked (either via "all:programId" or a specific round whose program we
|
||||||
|
// can resolve). Otherwise show every round across every program.
|
||||||
|
const scopeProgramId = scope?.startsWith('all:')
|
||||||
|
? scope.slice(4)
|
||||||
|
: allRounds.find((r) => r.id === scope)?.programId
|
||||||
|
const rounds = scopeProgramId
|
||||||
|
? allRounds.filter((r) => r.programId === scopeProgramId)
|
||||||
|
: allRounds
|
||||||
|
|
||||||
const roundIds = rounds.map(r => r.id)
|
const roundIds = rounds.map(r => r.id)
|
||||||
|
|
||||||
const { data: comparison, isLoading: comparisonLoading } =
|
const { data: comparison, isLoading: comparisonLoading } =
|
||||||
@@ -1212,20 +1106,48 @@ function RoundPipelineTab() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ReportsPage() {
|
export default function ReportsPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const urlRound = searchParams.get('round')
|
||||||
|
|
||||||
|
const { data: programs } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
|
const stages = useMemo(
|
||||||
|
() => programs?.flatMap((p) =>
|
||||||
|
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map(
|
||||||
|
(s: { id: string; name: string; status: string }) => ({
|
||||||
|
id: s.id,
|
||||||
|
name: s.name,
|
||||||
|
status: s.status,
|
||||||
|
programId: p.id,
|
||||||
|
programName: `${p.year} Edition`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
) || [],
|
||||||
|
[programs],
|
||||||
|
)
|
||||||
|
|
||||||
const [pdfStageId, setPdfStageId] = useState<string | null>(null)
|
const [pdfStageId, setPdfStageId] = useState<string | null>(null)
|
||||||
|
|
||||||
const { data: pdfPrograms } = trpc.program.list.useQuery({ includeStages: true })
|
|
||||||
const pdfStages = pdfPrograms?.flatMap((p) =>
|
|
||||||
((p.stages ?? []) as Array<{ id: string; name: string; status: string }>).map((s: { id: string; name: string; status: string }) => ({ id: s.id, name: s.name, status: s.status, programName: `${p.year} Edition` }))
|
|
||||||
) || []
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pdfStages.length && !pdfStageId) {
|
if (stages.length && !pdfStageId) {
|
||||||
setPdfStageId(findDefaultRound(pdfStages) ?? pdfStages[0].id)
|
setPdfStageId(findDefaultRound(stages) ?? stages[0].id)
|
||||||
}
|
}
|
||||||
}, [pdfStages.length, pdfStageId])
|
}, [stages.length, pdfStageId, stages])
|
||||||
|
|
||||||
const selectedPdfStage = pdfStages.find((r) => r.id === pdfStageId)
|
// Top-level selection drives every single-round tab. Persisted to the URL
|
||||||
|
// so reloads and shared links preserve the view. Defaults to the newest
|
||||||
|
// program's "All Rounds" entry.
|
||||||
|
const defaultScope = programs?.length ? `all:${programs[0].id}` : null
|
||||||
|
const scope = urlRound ?? defaultScope
|
||||||
|
|
||||||
|
const setScope = (value: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams.toString())
|
||||||
|
params.set('round', value)
|
||||||
|
router.replace(`${pathname}?${params.toString()}`, { scroll: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPdfStage = stages.find((r) => r.id === pdfStageId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -1237,6 +1159,35 @@ export default function ReportsPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Top-level round selector — drives every tab below */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<label className="text-sm font-medium">Viewing:</label>
|
||||||
|
<Select value={scope ?? ''} onValueChange={setScope}>
|
||||||
|
<SelectTrigger className="w-[320px]">
|
||||||
|
<SelectValue placeholder="Select a round or edition" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{programs?.map((p) => (
|
||||||
|
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
||||||
|
{p.year} Edition — All Rounds
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
{stages.map((stage) => (
|
||||||
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
|
{stage.programName} — {stage.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
This selection applies to every tab except Cross-Round (which compares multiple rounds).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Tabs defaultValue="overview" className="space-y-6">
|
<Tabs defaultValue="overview" className="space-y-6">
|
||||||
<div className="flex items-center justify-between flex-wrap gap-4">
|
<div className="flex items-center justify-between flex-wrap gap-4">
|
||||||
@@ -1272,7 +1223,7 @@ export default function ReportsPage() {
|
|||||||
<SelectValue placeholder="Select stage for PDF" />
|
<SelectValue placeholder="Select stage for PDF" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{pdfStages.map((stage) => (
|
{stages.map((stage) => (
|
||||||
<SelectItem key={stage.id} value={stage.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{stage.name}
|
{stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -1290,11 +1241,11 @@ export default function ReportsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="overview">
|
<TabsContent value="overview">
|
||||||
<ReportsOverview />
|
<ReportsOverview scope={scope} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="analytics">
|
<TabsContent value="analytics">
|
||||||
<StageAnalytics />
|
<StageAnalytics scope={scope} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="cross-stage">
|
<TabsContent value="cross-stage">
|
||||||
@@ -1302,15 +1253,15 @@ export default function ReportsPage() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="consistency">
|
<TabsContent value="consistency">
|
||||||
<JurorConsistencyTab />
|
<JurorConsistencyTab scope={scope} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="diversity">
|
<TabsContent value="diversity">
|
||||||
<DiversityTab />
|
<DiversityTab scope={scope} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="pipeline">
|
<TabsContent value="pipeline">
|
||||||
<RoundPipelineTab />
|
<RoundPipelineTab scope={scope} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user