feat: per-round balanced-scoring toggle in side sheet
A Switch at the top of the project side panel writes useBalancedRanking onto Round.configJson via the existing round.update mutation. The flip is shared across all viewers because the value lives in the round's persisted config; hydration runs on every roundData refetch so the UI converges quickly when another admin flips it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<string>('')
|
||||
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<string, unknown>
|
||||
const saved = (cfg.criteriaWeights ?? {}) as Record<string, number>
|
||||
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<string, unknown>
|
||||
setUseBalanced((cfg.useBalancedRanking as boolean | undefined) ?? true)
|
||||
if (weightsInitialized.current) return
|
||||
const saved = (cfg.criteriaWeights ?? {}) as Record<string, number>
|
||||
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<string, unknown>
|
||||
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
|
||||
</div>
|
||||
) : projectDetail ? (
|
||||
<div className="mt-6 space-y-6">
|
||||
{/* Balanced-ranking toggle (per-round; persists across viewers) */}
|
||||
<div className="flex items-center justify-between rounded-lg border p-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium">Use balanced scoring for ranking</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Corrects for per-juror grading style. Off uses raw averages.
|
||||
</span>
|
||||
</div>
|
||||
<Switch checked={useBalanced} onCheckedChange={persistUseBalanced} />
|
||||
</div>
|
||||
{/* Stats summary */}
|
||||
{projectDetail.stats && (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
|
||||
Reference in New Issue
Block a user