/* Pages: Survey (single page), Clinic checklist (stepper + draft), Thank You */ const { useState: useSt, useEffect: useEf, useRef: useRf } = React; function SurveyPage() { return (
); } const DRAFT_KEY = 'vtc_clinic_draft_v1'; function ClinicPage() { const schema = window.VTC.clinicSchema; const sections = schema.sections; const [step, setStep] = useSt(0); const [values, setValues] = useSt(() => { try { return JSON.parse(localStorage.getItem(DRAFT_KEY)) || {}; } catch { return {}; } }); const [errors, setErrors] = useSt({}); const [busy, setBusy] = useSt(false); const [savedAt, setSavedAt] = useSt(null); const top = useRf(null); // persist draft useEf(() => { const id = setTimeout(() => { try { localStorage.setItem(DRAFT_KEY, JSON.stringify(values)); setSavedAt(Date.now()); } catch {} }, 400); return () => clearTimeout(id); }, [values]); const onChange = (id, v) => { setValues((p) => ({ ...p, [id]: v })); setErrors((p) => (p[id] ? { ...p, [id]: '' } : p)); }; const cur = sections[step]; const pct = Math.round(((step + 1) / sections.length) * 100); const validateStep = () => { const { errors: er, firstBad } = validateSections([cur], values); setErrors(er); if (firstBad) { window.toast({ kind: 'err', title: 'Còn mục chưa hoàn tất', sub: 'Vui lòng kiểm tra các ô tô đỏ.' }); const el = document.getElementById('f_' + firstBad); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 150, behavior: 'smooth' }); return false; } return true; }; const goTop = () => { if (top.current) window.scrollTo({ top: top.current.getBoundingClientRect().top + window.scrollY - 90, behavior: 'smooth' }); }; const next = () => { if (validateStep()) { setStep((s) => Math.min(s + 1, sections.length - 1)); goTop(); } }; const back = () => { setStep((s) => Math.max(s - 1, 0)); goTop(); }; const jump = (i) => { if (i <= step || validateStep()) { setStep(i); goTop(); } }; async function submit() { const { errors: er, firstBad } = validateSections(sections, values); setErrors(er); if (firstBad) { // jump to the section containing the first bad field const idx = sections.findIndex((s) => s.fields.some((f) => f.id === firstBad)); setStep(idx >= 0 ? idx : step); window.toast({ kind: 'err', title: 'Còn mục chưa hoàn tất', sub: 'Đã chuyển tới phần cần bổ sung.' }); return; } setBusy(true); try { await submitForm(schema.formName, { ...values, __type: 'bangkiem' }); localStorage.removeItem(DRAFT_KEY); window.navigate('/cam-on?type=bangkiem'); } catch { setBusy(false); window.toast({ kind: 'err', title: 'Gửi chưa thành công', sub: 'Dữ liệu của bạn vẫn được lưu nháp.' }); } } const last = step === sections.length - 1; return ({schema.description}
Bản nháp được lưu trên trình duyệt của bạn — có thể đóng và quay lại sau.
Công cụ hỗ trợ quyết định lâm sàng, không thay thế chẩn đoán của bác sĩ.
{c.sub}