// Klyvio — Questionnaire interactif (dark theme Klyvio) function Questionnaire({ answers, setAnswers, onFinish, onExit, company }) { const questions = window.KLYVIO_QUESTIONS; const dims = window.KLYVIO_DIMENSIONS; const [idx, setIdx] = useState(0); const [paused, setPaused] = useState(false); const [savedAt, setSavedAt] = useState(null); const [elapsed, setElapsed] = useState(0); const startRef = useRef(Date.now()); const q = questions[idx]; const dim = dims.find(d => d.id === q.dim); const currentAnswer = answers[q.id]; useEffect(() => { if (paused) return; const interval = setInterval(() => setElapsed(Math.floor((Date.now() - startRef.current) / 1000)), 1000); return () => clearInterval(interval); }, [paused]); useEffect(() => { setSavedAt(Date.now()); const t = setTimeout(() => setSavedAt(null), 2000); return () => clearTimeout(t); }, [answers]); const answer = (val) => setAnswers({ ...answers, [q.id]: val }); const pct = Math.round(((idx + 1) / questions.length) * 100); const remaining = Math.max(0, Math.round((questions.length - idx - 1) * 0.5)); const canNext = q.optional || (currentAnswer !== undefined && currentAnswer !== null && currentAnswer !== "" && !(Array.isArray(currentAnswer) && currentAnswer.length === 0)); const next = () => { if (idx < questions.length - 1) setIdx(idx + 1); else onFinish(); }; const prev = () => { if (idx > 0) setIdx(idx - 1); }; useEffect(() => { const handler = (e) => { if (paused) return; if (e.key === "ArrowRight" && canNext) next(); else if (e.key === "ArrowLeft") prev(); else if (e.key >= "1" && e.key <= "9" && (q.type === "scale" || q.type === "single")) { const n = parseInt(e.key, 10) - 1; const opts = q.type === "scale" ? q.scale : q.options; if (opts && n < opts.length) answer(n); } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [idx, currentAnswer, paused]); return (
{/* Progress header */}
{dim.label}
Question {idx + 1} / {questions.length}
~{remaining} min restant {savedAt ? ( Sauvegardé ) : ( Sauvegarde auto )}
{/* Question body */}
{String(idx + 1).padStart(2, "0")} · {dim.label}

{q.q}

{q.hint && (
{q.hint}
)}
{/* Footer navigation */}
/ pour naviguer {(q.type === "scale" || q.type === "single") && <> · 1–9 pour répondre}
{/* Pause modal */} {paused && setPaused(false)} onSave={onExit} elapsed={elapsed} pct={pct} />}
); } function QuestionInput({ q, value, onChange }) { /* ── SCALE ── */ if (q.type === "scale") { return (
{q.scale.map((opt, i) => { const active = value === i; return ( ); })}
); } /* ── SINGLE ── */ if (q.type === "single") { return (
{q.options.map((opt, i) => { const active = value === i; return ( ); })}
); } /* ── MULTI ── */ if (q.type === "multi") { const sel = Array.isArray(value) ? value : []; const maxSelect = q.maxSelect || Infinity; const limitReached = sel.length >= maxSelect; const toggle = (i) => { if (sel.includes(i)) { // Toujours autoriser la désélection onChange(sel.filter(x => x !== i)); } else if (!limitReached) { // N'ajouter que si la limite n'est pas atteinte onChange([...sel, i]); } }; return (
{/* Compteur affiché uniquement si maxSelect est défini */} {q.maxSelect && (
{sel.length} / {q.maxSelect} sélectionné{sel.length > 1 ? "s" : ""}
{limitReached && (
Maximum atteint
)}
)}
{q.options.map((opt, i) => { const active = sel.includes(i); // Grisé si limite atteinte ET non sélectionné const dimmed = limitReached && !active; return ( ); })}
); } /* ── SLIDER ── */ if (q.type === "slider") { const v = value !== undefined && value !== null ? value : q.default; // Initialisation propre — seulement si la clé n'existe pas encore dans answers useEffect(() => { if (value === undefined || value === null) { onChange(q.default); } }, [q.id]); // dépend de q.id pour ne pas re-déclencher sur re-render const pct = ((v - q.min) / (q.max - q.min)) * 100; const displayVal = q.format === "currency-m" ? `${v}\u202fM€` : `${v}`; return (
{displayVal}
{q.unit && q.format !== "currency-m" && (
{q.unit}
)}
onChange(parseFloat(e.target.value))} style={{ width: "100%", height: 6, appearance: "none", background: `linear-gradient(to right, var(--accent) 0%, var(--accent) ${pct}%, var(--gray-dim) ${pct}%, var(--gray-dim) 100%)`, borderRadius: 999, cursor: "pointer", outline: "none", }} />
{q.min}{q.unit && q.format !== "currency-m" ? `\u202f${q.unit}` : q.format === "currency-m" ? "\u202fM€" : ""} {q.max}{q.unit && q.format !== "currency-m" ? `\u202f${q.unit}` : q.format === "currency-m" ? "\u202fM€" : ""}
); } /* ── TEXT ── */ if (q.type === "text") { // Garantit que la valeur est toujours une string — jamais un tableau ou un nombre parasite const safeValue = typeof value === "string" ? value : ""; return (