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 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-16 09:30:19 +01:00
parent 8e5fc18da6
commit 93f4ad4b31
7 changed files with 55 additions and 22 deletions

View File

@ -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) {

View File

@ -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 (
<Card>
@ -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: () => {

View File

@ -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) {

View File

@ -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: () => {

View File

@ -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 (

View File

@ -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: () => {

View File

@ -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({