feat: resolve observer project page round default and add selector

The observer full project page used to call getProjectDetail without
a round, getting cross-round contaminated stats. It now resolves a
default — the currently OPEN round the project is in, falling back
to the most recently CLOSED one — and renders a selector chip in
the score card whenever the project participated in more than one
candidate round. Initial selection respects the ?round= query param.

A new observer procedure (getProjectRoundsForObserver) returns the
project's open or closed rounds for the picker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-27 13:18:36 +02:00
parent cfd9dc6afe
commit 6f3e8885e0
3 changed files with 67 additions and 4 deletions

View File

@@ -2187,6 +2187,26 @@ export const analyticsRouter = router({
}
}),
/**
* Returns rounds the project has participated in, restricted to those that
* are open or already closed. Used by the observer full project page to
* resolve a default round when none is specified in the URL.
*/
getProjectRoundsForObserver: observerProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ ctx, input }) => {
const states = await ctx.prisma.projectRoundState.findMany({
where: { projectId: input.projectId },
select: {
round: { select: { id: true, name: true, status: true, sortOrder: true } },
},
})
return states
.map((s) => s.round)
.filter((r) => r.status === 'ROUND_ACTIVE' || r.status === 'ROUND_CLOSED')
.sort((a, b) => a.sortOrder - b.sortOrder)
}),
getRecentFiles: observerProcedure
.input(z.object({ roundId: z.string(), limit: z.number().min(1).max(50).default(10) }))
.query(async ({ ctx, input }) => {