// East Quabbin Health — screens
// Launch, coordinator, wizard steps, signature, confirmation
// ─────────────────────────────────────────────
// Launch screen (resident-first)
// ─────────────────────────────────────────────
const EqLaunchScreen = ({ t, onStart, onCoordinator, onResume }) => (
{/* ambient shapes */}
{/* Brand */}
{/* Big welcome */}
{t.launch.title}
{t.launch.sub}
{t.launch.time}
{t.launch.private}
{/* CTAs */}
{t.tagline}
);
// ─────────────────────────────────────────────
// Coordinator mode (staff preflight)
// ─────────────────────────────────────────────
const EqCoordScreen = ({ t, coord, setCoord, onBegin, onBack }) => (
);
// ─────────────────────────────────────────────
// Step 1 — About you
// ─────────────────────────────────────────────
const EqAbout = ({ t, data, set }) => (
);
// ─────────────────────────────────────────────
// Step 2 — Contact
// ─────────────────────────────────────────────
const EqContact = ({ t, data, set }) => (
);
// ─────────────────────────────────────────────
// Step 3 — Household
// ─────────────────────────────────────────────
const EqHouse = ({ t, data, set }) => (
{[
{ k: 'adults', label: t.house.adults },
{ k: 'kids', label: t.house.kids },
{ k: 'elders', label: t.house.elders },
].map(({ k, label }) => (
set(k, v)} />
))}
set('livesAlone', !data.livesAlone)}>
{t.house.livesAlone}
set('caregiver', !data.caregiver)}>
{t.house.caregiver}
{t.house.langOpts.map((o) => (
))}
);
// ─────────────────────────────────────────────
// Step — Daily activities (Katz ADL + Lawton IADL)
// ─────────────────────────────────────────────
const EqDaily = ({ t, data, set }) => {
const adl = data.adl || {};
const iadl = data.iadl || {};
const setAdl = (k, v) => set('adl', { ...adl, [k]: v });
const setIadl = (k, v) => set('iadl', { ...iadl, [k]: v });
const Grid = ({ items, values, onSet }) => (
{items.map((row, i) => (
{row.l}
{t.daily.levels.map(lvl => {
const on = values[row.k] === lvl.v;
return (
);
})}
))}
);
return (
{t.daily.adlHead}
{t.daily.adlHint}
{t.daily.iadlHead}
{t.daily.iadlHint}
);
};
// ─────────────────────────────────────────────
// Step — SDoH (Life & community)
// ─────────────────────────────────────────────
const EqSdoh = ({ t, data, set }) => {
const [openK, setOpenK] = React.useState(t.sdoh.domains[0]?.k);
const sdoh = data.sdoh || {};
const setSd = (k, v) => set('sdoh', { ...sdoh, [k]: v });
const answered = (domain) => domain.qs.filter(q => {
const a = sdoh[q.k];
return q.multi ? (Array.isArray(a) && a.length > 0) : !!a;
}).length;
return (
{t.sdoh.privacy}
{t.sdoh.domains.map(domain => {
const open = openK === domain.k;
const done = answered(domain);
const total = domain.qs.length;
return (
{open && (
{domain.optionalNote && (
{domain.optionalNote}
)}
{domain.qs.map(q => {
const a = sdoh[q.k];
return (
{q.q}
{q.opts.map(opt => {
const on = q.multi
? (Array.isArray(a) && a.includes(opt.v))
: a === opt.v;
return (
);
})}
);
})}
)}
);
})}
);
};
// ─────────────────────────────────────────────
// Step — Health (gated if "share" consent was not given)
// ─────────────────────────────────────────────
const EqHealth = ({ t, data, set }) => {
const toggleCond = (c) => {
const list = data.conditions || [];
const next = list.includes(c) ? list.filter(x => x !== c) : [...list, c];
set('conditions', next);
};
const choice = data.healthChoice; // 'now' | 'later' | undefined
if (!choice) {
return (
{t.health.chooseHead}
{t.health.chooseBody}
{t.health.chooseNote}
);
}
if (choice === 'later') {
return (
{t.health.laterHead}
{t.health.laterBody}
);
}
return (
{t.health.condOpts.map((o) => (
))}
{t.health.providerOpts.map((o) => (
))}
{t.health.insuranceOpts.map((o) => (
set('insurance', o)}>
{o}
))}
);
};
// ─────────────────────────────────────────────
// Step 5 — Access
// ─────────────────────────────────────────────
const EqAccess = ({ t, data, set }) => {
const toggleMob = (c) => {
const list = data.mobility || [];
const next = list.includes(c) ? list.filter(x => x !== c) : [...list, c];
set('mobility', next);
};
return (
);
};
// ─────────────────────────────────────────────
// Step 6 — Services (card grid)
// ─────────────────────────────────────────────
const EqServices = ({ t, data, set }) => {
const toggle = (k) => {
const list = data.services || [];
const next = list.includes(k) ? list.filter(x => x !== k) : [...list, k];
set('services', next);
};
const iconFor = { visits: 'heart', rides: 'car', meds: 'pill', wellness: 'phone', meals: 'home', mental: 'users', equip: 'hand' };
const accent = { visits: 'var(--eq-berry)', rides: 'var(--eq-sage)', meds: 'var(--eq-clay)', wellness: 'var(--eq-sky)', meals: 'var(--eq-butter)', mental: 'var(--eq-sage)', equip: 'var(--eq-clay)' };
return (
{t.services.serviceOpts.map((s) => {
const on = (data.services || []).includes(s.key);
return (
);
})}
);
};
// ─────────────────────────────────────────────
// Step 7 — Consent
// ─────────────────────────────────────────────
const EqConsent = ({ t, data, set }) => {
const toggle = (k) => set('consents', { ...(data.consents || {}), [k]: !(data.consents || {})[k] });
const togglePhysical = (k) => {
const cur = (data.consents || {})[k + '_physical'];
set('consents', { ...(data.consents || {}), [k + '_physical']: !cur });
};
return (
{t.consent.items.map((c) => {
if (c.physical) {
const requested = !!(data.consents || {})[c.key + '_physical'];
return (
{c.title}
Paper form
{c.body}
);
}
const on = !!(data.consents || {})[c.key];
return (
);
})}
);
};
// ─────────────────────────────────────────────
// Step 8 — Signature
// ─────────────────────────────────────────────
const EqSign = ({ t, data, set }) => {
const [mode, setMode] = React.useState(data.sigMode || 'draw');
const canvasRef = React.useRef(null);
const drawing = React.useRef(false);
const last = React.useRef(null);
const resize = () => {
const c = canvasRef.current; if (!c) return;
const rect = c.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
c.width = rect.width * dpr;
c.height = rect.height * dpr;
const ctx = c.getContext('2d');
ctx.scale(dpr, dpr);
ctx.lineWidth = 2.2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#1C2620';
};
React.useEffect(() => { resize(); }, [mode]);
const xy = (e) => {
const c = canvasRef.current; const rect = c.getBoundingClientRect();
const point = e.touches ? e.touches[0] : e;
return [point.clientX - rect.left, point.clientY - rect.top];
};
const start = (e) => {
e.preventDefault(); drawing.current = true; last.current = xy(e);
set('hasSig', true);
};
const move = (e) => {
if (!drawing.current) return;
e.preventDefault();
const [x, y] = xy(e);
const ctx = canvasRef.current.getContext('2d');
ctx.beginPath();
ctx.moveTo(last.current[0], last.current[1]);
ctx.lineTo(x, y);
ctx.stroke();
last.current = [x, y];
};
const end = () => { drawing.current = false; };
const clear = () => {
const c = canvasRef.current;
const ctx = c.getContext('2d');
ctx.clearRect(0, 0, c.width, c.height);
set('hasSig', false);
};
return (
{[{ k: 'draw', l: t.sign.draw }, { k: 'type', l: t.sign.typed }].map((m) => (
))}
{mode === 'draw' ? (
× Sign above
) : (
{ set('typedSig', e.target.value); set('hasSig', !!e.target.value); }}
placeholder={t.sign.typeHere}
style={{ fontFamily: 'var(--eq-serif)', fontSize: 22, height: 72 }} />
)}
{t.sign.agree}
);
};
// ─────────────────────────────────────────────
// Confirmation
// ─────────────────────────────────────────────
const EqDone = ({ t, code, onRestart, onPrint }) => (
{t.done.title}
{t.done.sub}
{t.done.codeLabel}
{code}
{t.done.next}
{t.done.steps.map((s, i) => (
))}
{t.done.callout}
);
Object.assign(window, {
EqLaunchScreen, EqCoordScreen,
EqAbout, EqContact, EqHouse, EqDaily, EqSdoh, EqHealth, EqAccess, EqServices, EqConsent, EqSign,
EqDone,
});