'use client' import { useState, useMemo, useCallback } from 'react' import { trpc } from '@/lib/trpc/client' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { Loader2 } from 'lucide-react' import { EvaluationFormBuilder } from '@/components/forms/evaluation-form-builder' import type { Criterion } from '@/components/forms/evaluation-form-builder' export type EvaluationCriteriaEditorProps = { roundId: string } export function EvaluationCriteriaEditor({ roundId }: EvaluationCriteriaEditorProps) { const [pendingCriteria, setPendingCriteria] = useState(null) const utils = trpc.useUtils() const { data: form, isLoading } = trpc.evaluation.getForm.useQuery( { roundId }, { refetchInterval: 30_000 }, ) const upsertMutation = trpc.evaluation.upsertForm.useMutation({ onSuccess: () => { utils.evaluation.getForm.invalidate({ roundId }) toast.success('Evaluation criteria saved') setPendingCriteria(null) }, onError: (err) => toast.error(err.message), }) // Convert server criteriaJson to Criterion[] format const serverCriteria: Criterion[] = useMemo(() => { if (!form?.criteriaJson) return [] return (form.criteriaJson as Criterion[]).map((c) => { // Handle legacy numeric-only format: convert "scale" string like "1-10" back to minScore/maxScore const type = c.type || 'numeric' if (type === 'numeric' && typeof c.scale === 'string') { const parts = (c.scale as string).split('-').map(Number) if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { return { ...c, type: 'numeric' as const, scale: parts[1], minScore: parts[0], maxScore: parts[1] } as unknown as Criterion } } return { ...c, type } as Criterion }) }, [form?.criteriaJson]) const handleChange = useCallback((criteria: Criterion[]) => { setPendingCriteria(criteria) }, []) const handleSave = () => { const criteria = pendingCriteria ?? serverCriteria const validCriteria = criteria.filter((c) => c.label.trim()) if (validCriteria.length === 0) { toast.error('Add at least one criterion') return } // Map to upsertForm format upsertMutation.mutate({ roundId, criteria: validCriteria.map((c) => ({ id: c.id, label: c.label, description: c.description, type: c.type || 'numeric', weight: c.weight, scale: typeof c.scale === 'number' ? c.scale : undefined, minScore: (c as any).minScore, maxScore: (c as any).maxScore, required: c.required, maxLength: c.maxLength, placeholder: c.placeholder, trueLabel: c.trueLabel, falseLabel: c.falseLabel, condition: c.condition, sectionId: c.sectionId, })), }) } return (
Evaluation Criteria {form ? `Version ${form.version} \u2014 ${(form.criteriaJson as Criterion[]).filter((c) => (c.type || 'numeric') !== 'section_header').length} criteria` : 'No criteria defined yet. Add numeric scores, yes/no questions, and text fields.'}
{pendingCriteria && (
)}
{isLoading ? (
{[1, 2, 3].map((i) => )}
) : ( )}
) }