'use client' import { useCallback, useEffect, useRef, useState } from 'react' import type { FormAnswers, Question, Survey } from '@/lib/types' import { clearDraft, loadDraft, saveDraft } from '@/lib/storage' import { getHiddenCodes, isQuestionVisible } from '@/lib/conditional' import { ProgressBar } from './progress-bar' import { QuestionRenderer } from './question-renderer' import { SummaryScreen } from './summary-screen' // Section metadata (code prefix → display title) // Prefixes: 'A' for A1-A7, '1' for 1.1-1.16, '2' for 2.1-2.5, etc. const SECTIONS: { prefix: string; title: string; sensitive?: boolean }[] = [ { prefix: 'A', title: 'Datos Generales' }, { prefix: '1', title: 'Prevalencia: cuidado y discapacidad', sensitive: true }, { prefix: '2', title: 'Intensidad y rol del cuidado' }, { prefix: '3', title: 'Impacto económico del cuidado' }, { prefix: '4', title: 'Tiempo y descanso' }, { prefix: '5', title: 'Impacto en el trabajo' }, { prefix: '6', title: 'Conciencia organizacional' }, { prefix: '7', title: 'Para cerrar' }, ] // Extracts the leading alpha or numeric segment from a question code. // 'A1' → 'A', '1.1' → '1', '1.16' → '1', '6.4' → '6', '7.1' → '7' function getSectionPrefix(code: string): string { const m = code.match(/^([A-Za-z]+|\d+)/) return m ? m[1] : code } interface SurveyFormProps { survey: Survey questions: Question[] } export function SurveyForm({ survey, questions }: SurveyFormProps) { const [answers, setAnswers] = useState({}) const [errors, setErrors] = useState>({}) const [submitted, setSubmitted] = useState(false) const [hydrated, setHydrated] = useState(false) // Announce when conditional blocks appear/disappear const [a11yAnnounce, setA11yAnnounce] = useState('') // Track which trigger codes had "Sí" on prev render to detect toggle const prevVisibility = useRef>({}) // Hydrate from localStorage on mount useEffect(() => { const draft = loadDraft(survey.id) if (Object.keys(draft).length > 0) setAnswers(draft) setHydrated(true) }, [survey.id]) // Persist to localStorage on every change (after hydration) useEffect(() => { if (!hydrated) return saveDraft(survey.id, answers) }, [answers, hydrated, survey.id]) const handleChange = useCallback( (code: string, value: FormAnswers[string]) => { setAnswers((prev) => { const updated = { ...prev, [code]: value } // Clear any dependent questions that become hidden const toHide = getHiddenCodes(code, value, questions, prev) for (const hiddenCode of toHide) { updated[hiddenCode] = undefined as unknown as FormAnswers[string] } // Announce block visibility change to screen readers if (toHide.length > 0) { setA11yAnnounce('Se ocultaron preguntas relacionadas y se limpiaron sus respuestas.') } else { // Check if a conditional block just became visible const appeared = questions.some( (q) => q.conditional_rules?.some((r) => r.if_question === code) && isQuestionVisible(q, updated) && !isQuestionVisible(q, prev) ) if (appeared) { setA11yAnnounce('Aparecieron nuevas preguntas según tu respuesta.') } } return updated }) // Clear validation error for this field setErrors((prev) => { if (!prev[code]) return prev const { [code]: _, ...rest } = prev return rest }) }, [questions] ) function validate(): boolean { const newErrors: Record = {} for (const q of questions) { if (!q.required) continue if (!isQuestionVisible(q, answers)) continue const val = answers[q.code] if ( val === null || val === undefined || val === '' || (Array.isArray(val) && val.length === 0) ) { newErrors[q.code] = 'Este campo es obligatorio.' } } setErrors(newErrors) return Object.keys(newErrors).length === 0 } function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!validate()) { // Move focus to first error const firstErrorCode = Object.keys(errors)[0] if (firstErrorCode) { document.getElementById(firstErrorCode)?.focus() } return } // 2A: no write to DB — just show summary clearDraft(survey.id) setSubmitted(true) } function handleStartOver() { setAnswers({}) setSubmitted(false) setErrors({}) } // Visible questions driven by conditional_rules const visibleQuestions = questions.filter((q) => isQuestionVisible(q, answers)) const answeredCount = visibleQuestions.filter((q) => { const v = answers[q.code] return v !== null && v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0) }).length if (submitted) { return ( ) } // Group visible questions by section prefix, in order type Section = { prefix: string; title: string; sensitive?: boolean; questions: Question[] } const sectionMap = new Map() for (const q of visibleQuestions) { const prefix = getSectionPrefix(q.code) if (!sectionMap.has(prefix)) { const meta = SECTIONS.find((s) => s.prefix === prefix) sectionMap.set(prefix, { prefix, title: meta?.title ?? prefix, sensitive: meta?.sensitive, questions: [], }) } sectionMap.get(prefix)!.questions.push(q) } return (
{/* Skip link for keyboard users */} Ir al formulario {/* Screen-reader live region for conditional announcements */}
{a11yAnnounce}

{survey.title}

Esta encuesta es anónima. Tus respuestas no te identifican.

{Array.from(sectionMap.values()).map((section) => (

{section.title}

{section.sensitive && (

Esta sección contiene preguntas sensibles sobre salud o discapacidad. Responder es completamente voluntario.

)}
{section.questions.map((q) => (
))}
))}
) }