177 lines
7.0 KiB
TypeScript
177 lines
7.0 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||
|
|
import { Input } from '@/components/ui/input'
|
||
|
|
import { Label } from '@/components/ui/label'
|
||
|
|
import { Switch } from '@/components/ui/switch'
|
||
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||
|
|
|
||
|
|
type DeliberationConfigProps = {
|
||
|
|
config: Record<string, unknown>
|
||
|
|
onChange: (config: Record<string, unknown>) => void
|
||
|
|
juryGroups?: Array<{ id: string; name: string }>
|
||
|
|
}
|
||
|
|
|
||
|
|
export function DeliberationConfig({ config, onChange, juryGroups }: DeliberationConfigProps) {
|
||
|
|
const update = (key: string, value: unknown) => {
|
||
|
|
onChange({ ...config, [key]: value })
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{/* Jury Group Selection */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className="text-base">Deliberation Jury</CardTitle>
|
||
|
|
<CardDescription>Which jury group participates in this deliberation round</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="space-y-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="juryGroupId">Jury Group</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
The jury group that will cast votes during deliberation
|
||
|
|
</p>
|
||
|
|
{juryGroups && juryGroups.length > 0 ? (
|
||
|
|
<Select
|
||
|
|
value={(config.juryGroupId as string) ?? ''}
|
||
|
|
onValueChange={(v) => update('juryGroupId', v)}
|
||
|
|
>
|
||
|
|
<SelectTrigger id="juryGroupId" className="w-72">
|
||
|
|
<SelectValue placeholder="Select a jury group" />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{juryGroups.map((g) => (
|
||
|
|
<SelectItem key={g.id} value={g.id}>{g.name}</SelectItem>
|
||
|
|
))}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
) : (
|
||
|
|
<Input
|
||
|
|
id="juryGroupId"
|
||
|
|
placeholder="Jury group ID"
|
||
|
|
value={(config.juryGroupId as string) ?? ''}
|
||
|
|
onChange={(e) => update('juryGroupId', e.target.value)}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Voting Settings */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className="text-base">Voting Settings</CardTitle>
|
||
|
|
<CardDescription>How deliberation votes are structured</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="space-y-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="mode">Deliberation Mode</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">How the final decision is made</p>
|
||
|
|
<Select
|
||
|
|
value={(config.mode as string) ?? 'SINGLE_WINNER_VOTE'}
|
||
|
|
onValueChange={(v) => update('mode', v)}
|
||
|
|
>
|
||
|
|
<SelectTrigger id="mode" className="w-64">
|
||
|
|
<SelectValue />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
<SelectItem value="SINGLE_WINNER_VOTE">Single Winner Vote</SelectItem>
|
||
|
|
<SelectItem value="FULL_RANKING">Full Ranking</SelectItem>
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="votingDuration">Voting Duration (min)</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">Time limit for voting round</p>
|
||
|
|
<Input
|
||
|
|
id="votingDuration"
|
||
|
|
type="number"
|
||
|
|
min={1}
|
||
|
|
className="w-32"
|
||
|
|
value={(config.votingDuration as number) ?? 60}
|
||
|
|
onChange={(e) => update('votingDuration', parseInt(e.target.value, 10) || 60)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="topN">Top N Projects</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">Number of finalists to select</p>
|
||
|
|
<Input
|
||
|
|
id="topN"
|
||
|
|
type="number"
|
||
|
|
min={1}
|
||
|
|
className="w-32"
|
||
|
|
value={(config.topN as number) ?? 3}
|
||
|
|
onChange={(e) => update('topN', parseInt(e.target.value, 10) || 3)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="tieBreakMethod">Tie Break Method</Label>
|
||
|
|
<Select
|
||
|
|
value={(config.tieBreakMethod as string) ?? 'ADMIN_DECIDES'}
|
||
|
|
onValueChange={(v) => update('tieBreakMethod', v)}
|
||
|
|
>
|
||
|
|
<SelectTrigger id="tieBreakMethod" className="w-64">
|
||
|
|
<SelectValue />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
<SelectItem value="ADMIN_DECIDES">Admin Decides</SelectItem>
|
||
|
|
<SelectItem value="RUNOFF">Runoff Vote</SelectItem>
|
||
|
|
<SelectItem value="SCORE_FALLBACK">Score Fallback (use prior scores)</SelectItem>
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Visibility & Overrides */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className="text-base">Visibility & Overrides</CardTitle>
|
||
|
|
<CardDescription>What information jurors can see during deliberation</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="space-y-4">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<Label htmlFor="showCollectiveRankings">Show Collective Rankings</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">Display aggregate rankings to jurors during voting</p>
|
||
|
|
</div>
|
||
|
|
<Switch
|
||
|
|
id="showCollectiveRankings"
|
||
|
|
checked={(config.showCollectiveRankings as boolean) ?? false}
|
||
|
|
onCheckedChange={(v) => update('showCollectiveRankings', v)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<Label htmlFor="showPriorJuryData">Show Prior Jury Data</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">Display evaluation scores from previous rounds</p>
|
||
|
|
</div>
|
||
|
|
<Switch
|
||
|
|
id="showPriorJuryData"
|
||
|
|
checked={(config.showPriorJuryData as boolean) ?? false}
|
||
|
|
onCheckedChange={(v) => update('showPriorJuryData', v)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<Label htmlFor="allowAdminOverride">Allow Admin Override</Label>
|
||
|
|
<p className="text-xs text-muted-foreground">Admin can override deliberation results</p>
|
||
|
|
</div>
|
||
|
|
<Switch
|
||
|
|
id="allowAdminOverride"
|
||
|
|
checked={(config.allowAdminOverride as boolean) ?? true}
|
||
|
|
onCheckedChange={(v) => update('allowAdminOverride', v)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|