diff --git a/src/components/admin/round/ranking-dashboard.tsx b/src/components/admin/round/ranking-dashboard.tsx index ac42464..1bd64e0 100644 --- a/src/components/admin/round/ranking-dashboard.tsx +++ b/src/components/admin/round/ranking-dashboard.tsx @@ -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) => ( - + {categoryLabels[category]} {evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? ( @@ -966,6 +975,26 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran ) : null} + {(() => { + 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 ( + + ) + })()} {localOrder[category].length === 0 ? ( diff --git a/src/server/routers/ranking.ts b/src/server/routers/ranking.ts index a2cb1ab..39c66f3 100644 --- a/src/server/routers/ranking.ts +++ b/src/server/routers/ranking.ts @@ -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.