feat(web): two-stage questionnaires, screening result and diary by result

- Sign-in: centred ЕМИАС card without the sidebar; anonymous entry asks «Депрессия / ПТСР».
- Stage 1 is the same for everyone and follows the fixed order of the MVP questionnaire:
  PHQ-2 → trauma filter/TSQ → RUS-AUDIT-S → C-SSRS → context; stage 2 only where stage 1
  is positive. Starts without a profile.
- After stage 1 the patient sees a preliminary result (screening, not a diagnosis), stored
  with the record; it switches on the diary modules (depression / PTSD).
- Stage 2 diary: morning (sleep, sleep quality, alcohol) and evening (mood, medication,
  3 PHQ-9 items in rotation, short PCL-5); C-SSRS since last contact only under risk;
  flags on weekly means against the personal norm; scheduled full scales with change
  (remission, response, worsening).
- Questionnaire log: GET /me/{token}/results and the «Мои результаты» screen.
- Questionnaires centred without the sidebar and without scale names; «Продолжить позже»
  goes home; confirmations are toasts; free-text comments are optional.
- Demo policy config/questionnaire_demo.yaml (INTAKE_DEMO only, not a clinical approval).

ADR-012.

Refs #77

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 15:14:09 +03:00
co-authored by Claude Opus 5.5
parent 6e7c89900b
commit 290b0d6151
36 changed files with 1900 additions and 691 deletions
+52 -46
View File
@@ -1,70 +1,76 @@
import { useNavigate } from "react-router-dom";
import { AppShell } from "../components/AppShell";
import { Icon } from "../components/Icon";
import { telHref, useHelp } from "../components/HelpSheet";
import { Icon, LogoMark } from "../components/Icon";
import { ru } from "../content/ru";
// A1 · Выбор способа входа
// A1 · Вход: без сайдбара, одна карточка по центру. ЕМИАС — основной путь, анонимный — по желанию
export function Welcome() {
const navigate = useNavigate();
const { resources, open } = useHelp();
const t = ru.welcome;
const emergency = resources[0];
return (
<AppShell signedIn={false}>
<div className="stack">
<header className="head">
<div className="signin-page">
<main className="signin" id="main">
<div className="signin__brand">
<LogoMark size={44} />
<span>
<span className="signin__name">{ru.product.name}</span>
<span className="signin__tag">{ru.product.tagline}</span>
</span>
</div>
<section className="signin__card">
<h1 className="h1">{t.title}</h1>
<p className="lead">{t.lead}</p>
</header>
<div className="entry-grid">
<button type="button" className="entry entry--emias" onClick={() => navigate("/emias")}>
<span className="entry__top">
<span className="tile-icon tile-icon--teal">
<Icon name="docPlus" size={26} />
</span>
<span className="chip chip--teal">{ru.common.recommended}</span>
</span>
<span className="entry__title">
{t.emias.title}
<Icon name="chevronRight" />
</span>
<span className="entry__text">{t.emias.text}</span>
</button>
<button type="button" className="entry" onClick={() => navigate("/anonymous")}>
<span className="entry__top">
<span className="tile-icon">
<Icon name="lock" size={26} />
<span className="entry__title">
{t.emias.title}
<Icon name="chevronRight" />
</span>
</span>
<span className="entry__title">
{t.anonymous.title}
<Icon name="chevronRight" />
</span>
<span className="entry__text">{t.anonymous.text}</span>
<ul className="signin__perks">
{t.emias.perks.map((p) => (
<li key={p}>
<Icon name="check" size={18} />
{p}
</li>
))}
</ul>
</button>
</div>
<section className="how" aria-labelledby="how-title">
<h2 id="how-title" className="h3">
{t.howTitle}
</h2>
<ol className="how__list">
{t.how.map((step, i) => (
<li key={step.title} className="how__item">
<span className="how__num">{i + 1}</span>
<span>
<span className="how__title">{step.title}</span>
<span className="how__text">{step.text}</span>
</span>
</li>
))}
</ol>
<div className="divider" role="separator">
<span>{t.or}</span>
</div>
<div className="stack stack--sm">
<button type="button" className="btn btn--ghost" onClick={() => navigate("/anonymous")}>
<Icon name="lock" size={20} />
{t.anonymous.title}
</button>
<p className="muted small center">{t.anonymous.text}</p>
</div>
</section>
<p className="muted small">{t.disclaimer}</p>
</div>
</AppShell>
<div className="signin__foot">
<button type="button" className="signin__sos" onClick={open}>
<Icon name="phone" size={18} />
{ru.help.block}
</button>
{emergency && (
<a className="signin__tel" href={telHref(emergency.phone)}>
{emergency.phone}
</a>
)}
</div>
<p className="muted small center">{t.disclaimer}</p>
</main>
</div>
);
}