Observer platform: mobile fixes, data/UX overhaul, animated nav
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m41s

- Fix dashboard default round selection to target active round instead of R1
- Move edition selector from dashboard header to hamburger menu via shared context
- Add observer-friendly status labels (Not Reviewed / Under Review / Reviewed)
- Fix pipeline completion: closed rounds show 100%, cap all rates at 100%
- Round badge on projects list shows furthest round reached
- Hide scores/evals for projects with zero evaluations
- Enhance project detail round history with pass/reject indicators from ProjectRoundState
- Remove irrelevant fields (Org Type, Budget, Duration) from project detail
- Clickable juror workload with expandable project assignments
- Humanize activity feed with icons and readable messages
- Fix jurors table: responsive card layout on mobile
- Fix criteria chart: horizontal bars for readable labels on mobile
- Animate hamburger menu open/close with CSS grid transition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 22:45:56 +01:00
parent 5eea430ebd
commit 213efdba87
11 changed files with 576 additions and 313 deletions

View File

@@ -0,0 +1,67 @@
'use client'
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
import { trpc } from '@/lib/trpc/client'
type Program = {
id: string
name: string | null
year?: number
rounds?: Array<{ id: string; name: string; status: string; competitionId?: string }>
}
type EditionContextValue = {
programs: Program[]
selectedProgramId: string
setSelectedProgramId: (id: string) => void
activeRoundId: string
}
const EditionContext = createContext<EditionContextValue | null>(null)
export function useEditionContext() {
const ctx = useContext(EditionContext)
if (!ctx) throw new Error('useEditionContext must be used within EditionProvider')
return ctx
}
function findBestRound(rounds: Array<{ id: string; status: string }>): string {
const active = rounds.find(r => r.status === 'ROUND_ACTIVE')
if (active) return active.id
const closed = [...rounds].filter(r => r.status === 'ROUND_CLOSED').pop()
if (closed) return closed.id
return rounds[0]?.id ?? ''
}
export function EditionProvider({ children }: { children: ReactNode }) {
const [selectedProgramId, setSelectedProgramId] = useState<string>('')
const { data: programs } = trpc.program.list.useQuery(
{ includeStages: true },
{ refetchInterval: 30_000 },
)
useEffect(() => {
if (programs && programs.length > 0 && !selectedProgramId) {
setSelectedProgramId(programs[0].id)
}
}, [programs, selectedProgramId])
const typedPrograms = (programs ?? []) as Program[]
const selectedProgram = typedPrograms.find(p => p.id === selectedProgramId)
const rounds = (selectedProgram?.rounds ?? []) as Array<{ id: string; status: string }>
const activeRoundId = findBestRound(rounds)
return (
<EditionContext.Provider
value={{
programs: typedPrograms,
selectedProgramId,
setSelectedProgramId,
activeRoundId,
}}
>
{children}
</EditionContext.Provider>
)
}