feat: reset to system-calculated ranking order
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m34s

Adds a 'Reset to system order' button per category (Startups and
Business Concepts). The button only appears when admins have
drag-reordered that category — otherwise there's nothing to reset.
Clicking it wipes that category's reorder history from the snapshot's
reordersJson via a new ranking.clearReorders mutation, after which
the dashboard re-initializes and falls back to the live composite
ranking (balanced/raw score, optionally blended with balanced pass
rate per the toggles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-27 14:47:32 +02:00
parent 900700f6ae
commit bfa9fb5c83
2 changed files with 56 additions and 1 deletions

View File

@@ -350,6 +350,15 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
// Do NOT invalidate getSnapshot — would reset localOrder
})
const clearReordersMutation = trpc.ranking.clearReorders.useMutation({
onSuccess: () => {
toast.success('Reverted to system-calculated order')
initialized.current = false // allow re-init on next snapshot load
void utils.ranking.getSnapshot.invalidate()
},
onError: (err) => toast.error(`Failed to reset: ${err.message}`),
})
const updateRoundMutation = trpc.round.update.useMutation({
onSuccess: () => {
toast.success('Ranking config saved')
@@ -953,7 +962,7 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
{/* Per-category sections */}
{(['STARTUP', 'BUSINESS_CONCEPT'] as const).map((category) => (
<Card key={category}>
<CardHeader>
<CardHeader className="flex-row items-center justify-between space-y-0">
<CardTitle className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
{categoryLabels[category]}
{evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? (
@@ -966,6 +975,26 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
</span>
) : null}
</CardTitle>
{(() => {
const reorders = (snapshot?.reordersJson as Array<{
category: 'STARTUP' | 'BUSINESS_CONCEPT'
}> | null) ?? []
const hasManualOrder = reorders.some((r) => r.category === category)
if (!hasManualOrder || !latestSnapshotId) return null
return (
<Button
variant="outline"
size="sm"
className="h-7 text-xs"
disabled={clearReordersMutation.isPending}
onClick={() => clearReordersMutation.mutate({ snapshotId: latestSnapshotId, category })}
title="Discard manual drag-reorder for this category and revert to system-calculated order"
>
<RefreshCw className="h-3 w-3 mr-1" />
Reset to system order
</Button>
)
})()}
</CardHeader>
<CardContent>
{localOrder[category].length === 0 ? (

View File

@@ -264,6 +264,32 @@ export const rankingRouter = router({
return { ok: true }
}),
/** Clear all manual drag-reorders for a snapshot, optionally filtered by
* category. The next dashboard render falls back to the system-computed
* order. Append-only history of reorders is wiped — that's the point. */
clearReorders: adminProcedure
.input(
z.object({
snapshotId: z.string(),
category: z.enum(['STARTUP', 'BUSINESS_CONCEPT']).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const snapshot = await ctx.prisma.rankingSnapshot.findUniqueOrThrow({
where: { id: input.snapshotId },
select: { reordersJson: true },
})
const existing = (snapshot.reordersJson as ReorderEvent[] | null) ?? []
const next = input.category
? existing.filter((r) => r.category !== input.category)
: []
await ctx.prisma.rankingSnapshot.update({
where: { id: input.snapshotId },
data: { reordersJson: next as unknown as Prisma.InputJsonValue },
})
return { ok: true, cleared: existing.length - next.length }
}),
/**
* RANK-09 — Manual trigger for auto-rank (admin button on round detail page).
* Reads ranking criteria from round configJson and executes quickRank.