From 93f4ad4b3168b1a6f588f1ea9ae15cec85fcbdc6 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 16 Feb 2026 09:30:19 +0100 Subject: [PATCH] Add auto-refresh polling across all admin and jury pages - Round detail page: 15s for live data (projects, assignments, scores, workload), 30s for config, 60s for static data - Filtering dashboard: 15s for results/stats, 30s for rules (job status already 2s) - Project states table: 15s polling - Coverage report: 15s polling - Jury round page: 30s for assignments and round data - Deliberation session: 10s polling for live vote updates - Admin dashboard: 30s for stats Co-Authored-By: Claude Opus 4.6 --- src/app/(admin)/admin/dashboard-content.tsx | 2 +- .../(admin)/admin/rounds/[roundId]/page.tsx | 42 ++++++++++++++----- .../jury/competitions/[roundId]/page.tsx | 4 +- .../deliberation/[sessionId]/page.tsx | 7 ++-- .../admin/assignment/coverage-report.tsx | 8 ++-- .../admin/round/filtering-dashboard.tsx | 13 +++++- .../admin/round/project-states-table.tsx | 1 + 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/app/(admin)/admin/dashboard-content.tsx b/src/app/(admin)/admin/dashboard-content.tsx index 26278d8..dae197c 100644 --- a/src/app/(admin)/admin/dashboard-content.tsx +++ b/src/app/(admin)/admin/dashboard-content.tsx @@ -253,7 +253,7 @@ function getActionIcon(action: string) { export function DashboardContent({ editionId, sessionName }: DashboardContentProps) { const { data, isLoading, error } = trpc.dashboard.getStats.useQuery( { editionId }, - { enabled: !!editionId, retry: 1 } + { enabled: !!editionId, retry: 1, refetchInterval: 30_000 } ) if (isLoading) { diff --git a/src/app/(admin)/admin/rounds/[roundId]/page.tsx b/src/app/(admin)/admin/rounds/[roundId]/page.tsx index 0307998..d4bb546 100644 --- a/src/app/(admin)/admin/rounds/[roundId]/page.tsx +++ b/src/app/(admin)/admin/rounds/[roundId]/page.tsx @@ -151,26 +151,35 @@ export default function RoundDetailPage() { const utils = trpc.useUtils() // ── Core data queries ────────────────────────────────────────────────── - const { data: round, isLoading } = trpc.round.getById.useQuery({ id: roundId }) - const { data: projectStates } = trpc.roundEngine.getProjectStates.useQuery({ roundId }) + const { data: round, isLoading } = trpc.round.getById.useQuery( + { id: roundId }, + { refetchInterval: 30_000 }, + ) + const { data: projectStates } = trpc.roundEngine.getProjectStates.useQuery( + { roundId }, + { refetchInterval: 15_000 }, + ) const competitionId = round?.competitionId ?? '' const { data: juryGroups } = trpc.juryGroup.list.useQuery( { competitionId }, - { enabled: !!competitionId }, + { enabled: !!competitionId, refetchInterval: 30_000 }, + ) + const { data: fileRequirements } = trpc.file.listRequirements.useQuery( + { roundId }, + { refetchInterval: 30_000 }, ) - const { data: fileRequirements } = trpc.file.listRequirements.useQuery({ roundId }) // Fetch awards linked to this round const { data: competition } = trpc.competition.getById.useQuery( { id: competitionId }, - { enabled: !!competitionId }, + { enabled: !!competitionId, refetchInterval: 60_000 }, ) const programId = competition?.programId const { data: awards } = trpc.specialAward.list.useQuery( { programId: programId! }, - { enabled: !!programId }, + { enabled: !!programId, refetchInterval: 60_000 }, ) const roundAwards = awards?.filter((a) => a.evaluationRoundId === roundId) ?? [] @@ -1184,6 +1193,7 @@ export default function RoundDetailPage() { function RoundUnassignedQueue({ roundId }: { roundId: string }) { const { data: unassigned, isLoading } = trpc.roundAssignment.unassignedQueue.useQuery( { roundId, requiredReviews: 3 }, + { refetchInterval: 15_000 }, ) return ( @@ -1235,7 +1245,10 @@ function RoundUnassignedQueue({ roundId }: { roundId: string }) { // ── Jury Progress Table ────────────────────────────────────────────────── function JuryProgressTable({ roundId }: { roundId: string }) { - const { data: workload, isLoading } = trpc.analytics.getJurorWorkload.useQuery({ roundId }) + const { data: workload, isLoading } = trpc.analytics.getJurorWorkload.useQuery( + { roundId }, + { refetchInterval: 15_000 }, + ) return ( @@ -1291,7 +1304,10 @@ function JuryProgressTable({ roundId }: { roundId: string }) { // ── Score Distribution ─────────────────────────────────────────────────── function ScoreDistribution({ roundId }: { roundId: string }) { - const { data: dist, isLoading } = trpc.analytics.getRoundScoreDistribution.useQuery({ roundId }) + const { data: dist, isLoading } = trpc.analytics.getRoundScoreDistribution.useQuery( + { roundId }, + { refetchInterval: 15_000 }, + ) const maxCount = useMemo(() => dist ? Math.max(...dist.globalDistribution.map((b) => b.count), 1) : 1, @@ -1428,7 +1444,10 @@ function IndividualAssignmentsTable({ roundId }: { roundId: string }) { const [newProjectId, setNewProjectId] = useState('') const utils = trpc.useUtils() - const { data: assignments, isLoading } = trpc.assignment.listByStage.useQuery({ roundId }) + const { data: assignments, isLoading } = trpc.assignment.listByStage.useQuery( + { roundId }, + { refetchInterval: 15_000 }, + ) const deleteMutation = trpc.assignment.delete.useMutation({ onSuccess: () => { @@ -1573,7 +1592,10 @@ function EvaluationCriteriaEditor({ roundId }: { roundId: string }) { }>>([]) const utils = trpc.useUtils() - const { data: form, isLoading } = trpc.evaluation.getForm.useQuery({ roundId }) + const { data: form, isLoading } = trpc.evaluation.getForm.useQuery( + { roundId }, + { refetchInterval: 30_000 }, + ) const upsertMutation = trpc.evaluation.upsertForm.useMutation({ onSuccess: () => { diff --git a/src/app/(jury)/jury/competitions/[roundId]/page.tsx b/src/app/(jury)/jury/competitions/[roundId]/page.tsx index 76529b1..5d36ef3 100644 --- a/src/app/(jury)/jury/competitions/[roundId]/page.tsx +++ b/src/app/(jury)/jury/competitions/[roundId]/page.tsx @@ -17,12 +17,12 @@ export default function JuryRoundDetailPage() { const { data: assignments, isLoading } = trpc.roundAssignment.getMyAssignments.useQuery( { roundId }, - { enabled: !!roundId } + { enabled: !!roundId, refetchInterval: 30_000 } ) const { data: round } = trpc.round.getById.useQuery( { id: roundId }, - { enabled: !!roundId } + { enabled: !!roundId, refetchInterval: 30_000 } ) if (isLoading) { diff --git a/src/app/(jury)/jury/competitions/deliberation/[sessionId]/page.tsx b/src/app/(jury)/jury/competitions/deliberation/[sessionId]/page.tsx index dbe1ba6..4149246 100644 --- a/src/app/(jury)/jury/competitions/deliberation/[sessionId]/page.tsx +++ b/src/app/(jury)/jury/competitions/deliberation/[sessionId]/page.tsx @@ -12,9 +12,10 @@ export default function JuryDeliberationPage({ params: paramsPromise }: { params const params = use(paramsPromise); const utils = trpc.useUtils(); - const { data: session, isLoading } = trpc.deliberation.getSession.useQuery({ - sessionId: params.sessionId - }); + const { data: session, isLoading } = trpc.deliberation.getSession.useQuery( + { sessionId: params.sessionId }, + { refetchInterval: 10_000 }, + ); const submitVoteMutation = trpc.deliberation.submitVote.useMutation({ onSuccess: () => { diff --git a/src/components/admin/assignment/coverage-report.tsx b/src/components/admin/assignment/coverage-report.tsx index 81e43d2..741acda 100644 --- a/src/components/admin/assignment/coverage-report.tsx +++ b/src/components/admin/assignment/coverage-report.tsx @@ -10,10 +10,10 @@ interface CoverageReportProps { } export function CoverageReport({ roundId }: CoverageReportProps) { - const { data: coverage, isLoading } = trpc.roundAssignment.coverageReport.useQuery({ - roundId, - requiredReviews: 3, - }) + const { data: coverage, isLoading } = trpc.roundAssignment.coverageReport.useQuery( + { roundId, requiredReviews: 3 }, + { refetchInterval: 15_000 }, + ) if (isLoading) { return ( diff --git a/src/components/admin/round/filtering-dashboard.tsx b/src/components/admin/round/filtering-dashboard.tsx index 0c42dd2..7a6e72f 100644 --- a/src/components/admin/round/filtering-dashboard.tsx +++ b/src/components/admin/round/filtering-dashboard.tsx @@ -108,13 +108,18 @@ export function FilteringDashboard({ competitionId, roundId }: FilteringDashboar // -- Queries -- const { data: stats, isLoading: statsLoading } = trpc.filtering.getResultStats.useQuery( { roundId }, + { refetchInterval: 15_000 }, ) const { data: latestJob, isLoading: jobLoading } = trpc.filtering.getLatestJob.useQuery( { roundId }, + { refetchInterval: 15_000 }, ) - const { data: rules } = trpc.filtering.getRules.useQuery({ roundId }) + const { data: rules } = trpc.filtering.getRules.useQuery( + { roundId }, + { refetchInterval: 30_000 }, + ) const { data: resultsPage, isLoading: resultsLoading } = trpc.filtering.getResults.useQuery( { @@ -123,6 +128,7 @@ export function FilteringDashboard({ competitionId, roundId }: FilteringDashboar page, perPage: 25, }, + { refetchInterval: 15_000 }, ) const { data: jobStatus } = trpc.filtering.getJobStatus.useQuery( @@ -1089,7 +1095,10 @@ function FilteringRulesSection({ roundId }: { roundId: string }) { const utils = trpc.useUtils() - const { data: rules, isLoading } = trpc.filtering.getRules.useQuery({ roundId }) + const { data: rules, isLoading } = trpc.filtering.getRules.useQuery( + { roundId }, + { refetchInterval: 30_000 }, + ) const createMutation = trpc.filtering.createRule.useMutation({ onSuccess: () => { diff --git a/src/components/admin/round/project-states-table.tsx b/src/components/admin/round/project-states-table.tsx index 9ac97ae..7ad483f 100644 --- a/src/components/admin/round/project-states-table.tsx +++ b/src/components/admin/round/project-states-table.tsx @@ -92,6 +92,7 @@ export function ProjectStatesTable({ competitionId, roundId }: ProjectStatesTabl const { data: projectStates, isLoading } = trpc.roundEngine.getProjectStates.useQuery( { roundId }, + { refetchInterval: 15_000 }, ) const transitionMutation = trpc.roundEngine.transitionProject.useMutation({