Files
PTSRaiAgent/web/src/components/Toast.tsx
T
Arxip222andClaude Opus 5.5 290b0d6151 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>
2026-10-04 15:14:09 +03:00

56 lines
1.7 KiB
TypeScript

import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from "react";
import { Icon } from "./Icon";
/*
* Short confirmations («отметил приём», «дневник сохранён») as a toast in the top-right corner instead of a separate
* confirmation page: the person lands on the home screen right away and still sees what happened.
*/
interface Toast {
id: number;
text: string;
}
const Ctx = createContext<(text: string) => void>(() => undefined);
const LIFETIME_MS = 6000;
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const next = useRef(0);
const close = useCallback((id: number) => setToasts((all) => all.filter((t) => t.id !== id)), []);
const show = useCallback(
(text: string) => {
const id = ++next.current;
setToasts((all) => [...all.slice(-2), { id, text }]);
window.setTimeout(() => close(id), LIFETIME_MS);
},
[close],
);
const value = useMemo(() => show, [show]);
return (
<Ctx.Provider value={value}>
{children}
<div className="toasts" role="status" aria-live="polite">
{toasts.map((t) => (
<div key={t.id} className="toast">
<span className="toast__icon" aria-hidden="true">
<Icon name="check" size={18} />
</span>
<p className="toast__text">{t.text}</p>
<button type="button" className="toast__close" onClick={() => close(t.id)} aria-label="Закрыть">
<Icon name="close" size={18} />
</button>
</div>
))}
</div>
</Ctx.Provider>
);
}
export function useToast(): (text: string) => void {
return useContext(Ctx);
}