diff --git a/src/components/admin/round/ranking-dashboard.tsx b/src/components/admin/round/ranking-dashboard.tsx index 4655c4d..32428da 100644 --- a/src/components/admin/round/ranking-dashboard.tsx +++ b/src/components/admin/round/ranking-dashboard.tsx @@ -37,6 +37,7 @@ import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Slider } from '@/components/ui/slider' +import { Switch } from '@/components/ui/switch' import { Collapsible, CollapsibleContent, @@ -271,6 +272,7 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran const [localCriteriaText, setLocalCriteriaText] = useState('') const [localScoreWeight, setLocalScoreWeight] = useState(5) const [localPassRateWeight, setLocalPassRateWeight] = useState(5) + const [useBalanced, setUseBalanced] = useState(true) const weightsInitialized = useRef(false) // ─── Sensors ────────────────────────────────────────────────────────────── @@ -471,18 +473,32 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran }, [evalForm]) // ─── Init local weights + criteriaText from round config ────────────────── + // useBalanced is hydrated on every roundData refetch (it has its own toggle + // that persists immediately), so it sits outside the once-only guard. useEffect(() => { - if (!weightsInitialized.current && roundData?.configJson) { - const cfg = roundData.configJson as Record - const saved = (cfg.criteriaWeights ?? {}) as Record - setLocalWeights(saved) - setLocalCriteriaText((cfg.rankingCriteria as string) ?? '') - setLocalScoreWeight((cfg.scoreWeight as number) ?? 5) - setLocalPassRateWeight((cfg.passRateWeight as number) ?? 5) - weightsInitialized.current = true - } + if (!roundData?.configJson) return + const cfg = roundData.configJson as Record + setUseBalanced((cfg.useBalancedRanking as boolean | undefined) ?? true) + if (weightsInitialized.current) return + const saved = (cfg.criteriaWeights ?? {}) as Record + setLocalWeights(saved) + setLocalCriteriaText((cfg.rankingCriteria as string) ?? '') + setLocalScoreWeight((cfg.scoreWeight as number) ?? 5) + setLocalPassRateWeight((cfg.passRateWeight as number) ?? 5) + weightsInitialized.current = true }, [roundData]) + // ─── Persist the balanced-ranking toggle immediately ───────────────────── + const persistUseBalanced = (next: boolean) => { + setUseBalanced(next) + if (!roundData?.configJson) return + const cfg = roundData.configJson as Record + updateRoundMutation.mutate({ + id: roundId, + configJson: { ...cfg, useBalancedRanking: next }, + }) + } + // ─── Save weights + criteria text to round config ───────────────────────── const saveRankingConfig = () => { if (!roundData?.configJson) return @@ -1001,6 +1017,16 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran ) : projectDetail ? (
+ {/* Balanced-ranking toggle (per-round; persists across viewers) */} +
+
+ Use balanced scoring for ranking + + Corrects for per-juror grading style. Off uses raw averages. + +
+ +
{/* Stats summary */} {projectDetail.stats && (