Con las mejoras visuales
This commit is contained in:
148
app/globals.css
148
app/globals.css
@@ -482,6 +482,154 @@ body {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════
|
||||
WIZARD (4D) — layout de altura estable (UX 1+2+3)
|
||||
1) viewport estable: enunciado anclado en el tercio superior, no centrado
|
||||
2) header (progreso) y footer (Anterior/Siguiente) fijos, fuera del área
|
||||
que cambia de tamaño — nunca "saltan" entre pantallas
|
||||
3) área de respuesta con altura máxima + scroll interno + fade inferior
|
||||
══════════════════════════════════════════════════════════════ */
|
||||
.wizard-shell {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100dvh - 160px); /* deja lugar al header/footer de marca (layout.tsx) */
|
||||
}
|
||||
|
||||
.wizard-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 12px 0;
|
||||
}
|
||||
.wizard-topbar-brand {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* Stage: altura fija, no auto-crece con el contenido de la pregunta actual. */
|
||||
.wizard-stage {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding-top: 48px; /* ancla el enunciado en el tercio superior, no centrado vertical */
|
||||
}
|
||||
|
||||
.wizard-section-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.wizard-section-number {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.wizard-section-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.wizard-section-desc {
|
||||
font-size: 1.0625rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.6;
|
||||
max-width: 480px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Enunciado: bloque con altura reservada constante, mismo lugar pantalla tras pantalla. */
|
||||
.wizard-prompt-block {
|
||||
min-height: 116px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.wizard-instance-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary-hover);
|
||||
background: var(--color-primary-light);
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.wizard-prompt-code {
|
||||
display: block;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: #7A8FA0;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.wizard-prompt-text {
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
line-height: 1.35;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.wizard-prompt-hint {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Área de respuesta: altura máxima + scroll interno + fade inferior cuando hay más abajo. */
|
||||
.wizard-answer-area {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.wizard-answer-area::after {
|
||||
content: '';
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
height: 28px;
|
||||
margin-top: -28px;
|
||||
background: linear-gradient(to bottom, transparent, var(--color-surface) 75%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wizard-bottombar {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 16px 0;
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.wizard-bottombar-hint {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wizard-prompt-text { font-size: 1.1875rem; }
|
||||
.wizard-section-title { font-size: 1.5rem; }
|
||||
.wizard-answer-area { max-height: 320px; }
|
||||
}
|
||||
|
||||
/* ── Loading skeleton ── */
|
||||
.loading-wrapper {
|
||||
max-width: 720px;
|
||||
|
||||
32
app/page.tsx
32
app/page.tsx
@@ -1,6 +1,13 @@
|
||||
import { createClient } from '@/lib/supabase'
|
||||
import type { BrandConfig, Question, Survey } from '@/lib/types'
|
||||
import { SurveyForm } from '@/components/survey-form'
|
||||
import type { BrandConfig, Survey } from '@/lib/types'
|
||||
import type { FormSchema, Question as EngineQuestion } from '@/lib/form-engine.types'
|
||||
import { buildFormSchema } from '@/lib/form-engine'
|
||||
import { WizardForm } from '@/components/wizard-form'
|
||||
|
||||
// Preguntas de consentimiento (S1, 1.1, 1.2): las resuelve ConsentScreen, no el
|
||||
// motor de flujo — su texto legal (ADR-003) no vive en el hint de estas filas
|
||||
// todavía. Se excluyen del FormSchema para que el wizard arranque en S2.
|
||||
const CONSENT_CODES = new Set(['S1', '1.1', '1.2'])
|
||||
|
||||
// Force dynamic rendering — page fetches from Supabase at request time.
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -48,11 +55,12 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Fetch questions ordered by position
|
||||
// 2. Fetch questions ordered by position — incluye hint/instance_generator/instance_of (4D)
|
||||
const { data: questionRows, error: questionsError } = await supabase
|
||||
.from('questions')
|
||||
.select(
|
||||
'id, survey_id, code, type, prompt, position, is_sensitive, required, max_select, options, conditional_rules'
|
||||
'id, survey_id, code, type, prompt, position, is_sensitive, required, hint, ' +
|
||||
'max_select, options, conditional_rules, instance_generator, instance_of'
|
||||
)
|
||||
.eq('survey_id', surveyRow.id)
|
||||
.order('position', { ascending: true })
|
||||
@@ -66,9 +74,12 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
const questions = questionRows as Question[]
|
||||
// Cast en dos pasos: el cliente Supabase no tiene un Database genérico configurado
|
||||
// (lib/supabase.ts), así que el tipo de fila que infiere para .select() no solapa
|
||||
// directamente con EngineQuestion. Las columnas pedidas arriba sí coinciden 1:1.
|
||||
const engineQuestions = questionRows as unknown as EngineQuestion[]
|
||||
|
||||
console.log(`[2B] Survey loaded: id=${surveyRow.id} | questions=${questions.length}`)
|
||||
console.log(`[4D] Survey loaded: id=${surveyRow.id} | questions=${engineQuestions.length}`)
|
||||
|
||||
const survey: Survey = {
|
||||
id: surveyRow.id,
|
||||
@@ -79,8 +90,13 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
k_threshold: surveyRow.k_threshold,
|
||||
consent_version_id: surveyRow.consent_version_id,
|
||||
brand_config: (surveyRow.brand_config as BrandConfig) ?? null,
|
||||
questions,
|
||||
questions: [],
|
||||
}
|
||||
|
||||
return <SurveyForm survey={survey} questions={questions} />
|
||||
// El consentimiento (S1, 1.1, 1.2) lo resuelve ConsentScreen, no el motor — ver nota arriba.
|
||||
const schema: FormSchema = buildFormSchema(
|
||||
engineQuestions.filter((q) => !CONSENT_CODES.has(q.code))
|
||||
)
|
||||
|
||||
return <WizardForm survey={survey} schema={schema} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user