Con las mejoras visuales

This commit is contained in:
markosbenitez
2026-06-23 21:44:05 -03:00
parent f2666c7d3c
commit c30688810a
5 changed files with 574 additions and 62 deletions

View File

@@ -1,93 +1,69 @@
'use client'
import type { FormAnswers, Question, QuestionOption, RatingOptions } from '@/lib/types'
import type { AnswerValue, Question, QuestionOption } from '@/lib/form-engine.types'
import { RadioGroup } from './widgets/radio-group'
import { CheckboxGroup } from './widgets/checkbox-group'
import { RatingScale } from './widgets/rating-scale'
import { TextAreaField } from './widgets/text-area-field'
import { TextShortField } from './widgets/text-short-field'
import { GeoSelectWidget } from './widgets/geo-select-widget'
interface QuestionRendererProps {
question: Question
answers: FormAnswers
onChange: (code: string, value: FormAnswers[string]) => void
errors: Record<string, string>
value: AnswerValue
onChange: (value: AnswerValue) => void
error?: string
}
function isArrayOptions(opts: unknown): opts is QuestionOption[] {
return Array.isArray(opts)
}
const YES_NO_OPTIONS: QuestionOption[] = [
{ value: 'true', label: 'Sí' },
{ value: 'false', label: 'No' },
]
function isRatingOptions(opts: unknown): opts is RatingOptions {
return typeof opts === 'object' && opts !== null && 'min' in opts && 'max' in opts
}
export function QuestionRenderer({
question,
answers,
onChange,
errors,
}: QuestionRendererProps) {
export function QuestionRenderer({ question, value, onChange, error }: QuestionRendererProps) {
const { code, type, prompt, required, max_select, options } = question
const error = errors[code]
const legend = (
<>
<span className="q-code">{code} ·</span> {prompt}
</>
)
// A1 (Departamento) renders a combined geo widget that also handles A2 (Ciudad)
// Code badges for A1/A2 are rendered inside GeoSelectWidget.
if (code === 'A1') {
return (
<GeoSelectWidget
departmentValue={(answers['A1'] as string) ?? ''}
cityValue={(answers['A2'] as string) ?? ''}
onDepartmentChange={(v) => onChange('A1', v)}
onCityChange={(v) => onChange('A2', v)}
departmentError={errors['A1']}
cityError={errors['A2']}
/>
)
}
// A2 is rendered inside the GeoSelectWidget above — skip here to avoid duplication
if (code === 'A2') return null
const legend = <><span className="q-code">{code} ·</span> {prompt}</>
if (type === 'single_choice' && isArrayOptions(options)) {
if (type === 'single_choice' && options) {
return (
<RadioGroup
name={code}
legend={legend}
options={options}
value={answers[code] as string | undefined}
value={(value as string) ?? undefined}
required={required}
onChange={(v) => onChange(code, v)}
onChange={onChange}
error={error}
/>
)
}
if (type === 'multiple_choice' && isArrayOptions(options)) {
if (type === 'multiple_choice' && options) {
return (
<CheckboxGroup
name={code}
legend={legend}
options={options}
value={(answers[code] as string[]) ?? []}
value={(value as string[]) ?? []}
maxSelect={max_select}
required={required}
onChange={(v) => onChange(code, v)}
onChange={onChange}
error={error}
/>
)
}
if (type === 'rating' && isRatingOptions(options)) {
// yes_no resuelto con el RadioGroup existente — dos opciones fijas, sin widget nuevo.
if (type === 'yes_no') {
return (
<RatingScale
<RadioGroup
name={code}
legend={legend}
ratingOptions={options}
value={answers[code] as number | undefined}
options={YES_NO_OPTIONS}
value={(value as string) ?? undefined}
required={required}
onChange={(v) => onChange(code, v)}
onChange={onChange}
error={error}
/>
)
@@ -98,9 +74,9 @@ export function QuestionRenderer({
<TextShortField
name={code}
label={legend}
value={(answers[code] as string) ?? ''}
value={(value as string) ?? ''}
required={required}
onChange={(v) => onChange(code, v)}
onChange={onChange}
error={error}
/>
)
@@ -111,9 +87,9 @@ export function QuestionRenderer({
<TextAreaField
name={code}
label={legend}
value={(answers[code] as string) ?? ''}
value={(value as string) ?? ''}
required={required}
onChange={(v) => onChange(code, v)}
onChange={onChange}
error={error}
/>
)