feat: reset to system-calculated ranking order
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m34s
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:
@@ -350,6 +350,15 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
|
|||||||
// Do NOT invalidate getSnapshot — would reset localOrder
|
// 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({
|
const updateRoundMutation = trpc.round.update.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('Ranking config saved')
|
toast.success('Ranking config saved')
|
||||||
@@ -953,7 +962,7 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
|
|||||||
{/* Per-category sections */}
|
{/* Per-category sections */}
|
||||||
{(['STARTUP', 'BUSINESS_CONCEPT'] as const).map((category) => (
|
{(['STARTUP', 'BUSINESS_CONCEPT'] as const).map((category) => (
|
||||||
<Card key={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">
|
<CardTitle className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
{categoryLabels[category]}
|
{categoryLabels[category]}
|
||||||
{evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? (
|
{evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? (
|
||||||
@@ -966,6 +975,26 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</CardTitle>
|
</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>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{localOrder[category].length === 0 ? (
|
{localOrder[category].length === 0 ? (
|
||||||
|
|||||||
@@ -264,6 +264,32 @@ export const rankingRouter = router({
|
|||||||
return { ok: true }
|
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).
|
* RANK-09 — Manual trigger for auto-rank (admin button on round detail page).
|
||||||
* Reads ranking criteria from round configJson and executes quickRank.
|
* Reads ranking criteria from round configJson and executes quickRank.
|
||||||
|
|||||||
Reference in New Issue
Block a user