Fix award source round dropdown — auto-resolve competitionId from program
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m19s

Awards created from /admin/awards/new only sent programId, leaving
competitionId null. The edit page's source round dropdown was empty
because it depended on competitionId to fetch competition rounds.

- create mutation: auto-resolve competitionId from program's latest competition
- update mutation: backfill competitionId on save if missing
- get query: backfill competitionId on read for legacy awards
- edit page: use award.competition.rounds directly instead of separate query

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 21:00:20 +01:00
parent 619206c03f
commit 70d24036f9
2 changed files with 55 additions and 15 deletions

View File

@@ -38,12 +38,8 @@ export default function EditAwardPage({
const utils = trpc.useUtils() const utils = trpc.useUtils()
const { data: award, isLoading } = trpc.specialAward.get.useQuery({ id: awardId }) const { data: award, isLoading } = trpc.specialAward.get.useQuery({ id: awardId })
// Fetch competition rounds for source round selector // Rounds come from the award's included competition relation
const competitionId = award?.competitionId const competitionRounds = award?.competition?.rounds ?? []
const { data: competition } = trpc.competition.getById.useQuery(
{ id: competitionId! },
{ enabled: !!competitionId }
)
const updateAward = trpc.specialAward.update.useMutation({ const updateAward = trpc.specialAward.update.useMutation({
onSuccess: () => { onSuccess: () => {
@@ -266,13 +262,11 @@ export default function EditAwardPage({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="none">No source round</SelectItem> <SelectItem value="none">No source round</SelectItem>
{competition?.rounds {competitionRounds.map((round) => (
?.sort((a, b) => a.sortOrder - b.sortOrder) <SelectItem key={round.id} value={round.id}>
.map((round) => ( {round.name} ({round.roundType})
<SelectItem key={round.id} value={round.id}> </SelectItem>
{round.name} ({round.roundType}) ))}
</SelectItem>
))}
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">

View File

@@ -70,12 +70,30 @@ export const specialAwardRouter = router({
}, },
}) })
// Auto-resolve competition if missing (legacy awards created without competitionId)
let { competition } = award
if (!competition && award.programId) {
const comp = await ctx.prisma.competition.findFirst({
where: { programId: award.programId },
orderBy: { createdAt: 'desc' },
select: { id: true, name: true, rounds: { select: { id: true, name: true, roundType: true, sortOrder: true }, orderBy: { sortOrder: 'asc' as const } } },
})
if (comp) {
competition = comp
// Backfill competitionId on the award
await ctx.prisma.specialAward.update({
where: { id: input.id },
data: { competitionId: comp.id },
})
}
}
// Count eligible projects // Count eligible projects
const eligibleCount = await ctx.prisma.awardEligibility.count({ const eligibleCount = await ctx.prisma.awardEligibility.count({
where: { awardId: input.id, eligible: true }, where: { awardId: input.id, eligible: true },
}) })
return { ...award, eligibleCount } return { ...award, competition, eligibleCount }
}), }),
// ─── Admin Mutations ──────────────────────────────────────────────────── // ─── Admin Mutations ────────────────────────────────────────────────────
@@ -100,6 +118,17 @@ export const specialAwardRouter = router({
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
// Auto-resolve competitionId from program if not provided
let competitionId = input.competitionId
if (!competitionId) {
const comp = await ctx.prisma.competition.findFirst({
where: { programId: input.programId },
orderBy: { createdAt: 'desc' },
select: { id: true },
})
competitionId = comp?.id ?? undefined
}
const maxOrder = await ctx.prisma.specialAward.aggregate({ const maxOrder = await ctx.prisma.specialAward.aggregate({
where: { programId: input.programId }, where: { programId: input.programId },
_max: { sortOrder: true }, _max: { sortOrder: true },
@@ -114,7 +143,7 @@ export const specialAwardRouter = router({
useAiEligibility: input.useAiEligibility ?? true, useAiEligibility: input.useAiEligibility ?? true,
scoringMode: input.scoringMode, scoringMode: input.scoringMode,
maxRankedPicks: input.maxRankedPicks, maxRankedPicks: input.maxRankedPicks,
competitionId: input.competitionId, competitionId,
evaluationRoundId: input.evaluationRoundId, evaluationRoundId: input.evaluationRoundId,
juryGroupId: input.juryGroupId, juryGroupId: input.juryGroupId,
eligibilityMode: input.eligibilityMode, eligibilityMode: input.eligibilityMode,
@@ -160,6 +189,23 @@ export const specialAwardRouter = router({
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const { id, ...rest } = input const { id, ...rest } = input
// Auto-resolve competitionId if missing on existing award
if (rest.competitionId === undefined) {
const existing = await ctx.prisma.specialAward.findUnique({
where: { id },
select: { competitionId: true, programId: true },
})
if (existing && !existing.competitionId) {
const comp = await ctx.prisma.competition.findFirst({
where: { programId: existing.programId },
orderBy: { createdAt: 'desc' },
select: { id: true },
})
if (comp) rest.competitionId = comp.id
}
}
const award = await ctx.prisma.specialAward.update({ const award = await ctx.prisma.specialAward.update({
where: { id }, where: { id },
data: rest, data: rest,