feat(web): full desktop layout instead of a stretched phone view
- focus screens: question and context on the left, answers on the right in one wide card; consent text and the 0–10 scale in one wide column - today: dashboard with tasks, this week's doses and quick actions side by side; meds / doctor / profile in two-column grids; welcome gets «Как это работает» steps - fix: 0–10 scale no longer overflows narrow containers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+165
-103
@@ -1,12 +1,13 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import type { TodayDose } from "../api/types";
|
||||
import type { TodayDose, TodayPrescription } from "../api/types";
|
||||
import { AppShell, Notice } from "../components/AppShell";
|
||||
import { Icon } from "../components/Icon";
|
||||
import { ru } from "../content/ru";
|
||||
import { useDialog } from "../dialog/DialogContext";
|
||||
import { classify } from "../dialog/step";
|
||||
import { useSession } from "../session/SessionContext";
|
||||
import { WeekDots } from "./Meds";
|
||||
import { humanDate, useToday } from "./useToday";
|
||||
|
||||
function VisitCard({ date, onOpen }: { date: string; onOpen: () => void }) {
|
||||
@@ -24,6 +25,26 @@ function VisitCard({ date, onOpen }: { date: string; onOpen: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
function WeekCard({ rx, onOpen }: { rx: TodayPrescription; onOpen: () => void }) {
|
||||
const taken = rx.week.filter((w) => w.status === "taken").length;
|
||||
const counted = rx.week.filter((w) => w.status !== "none").length;
|
||||
return (
|
||||
<button type="button" className="card weekcard" onClick={onOpen}>
|
||||
<span className="card__head">
|
||||
<span className="tile-icon tile-icon--teal">
|
||||
<Icon name="pill" />
|
||||
</span>
|
||||
<span className="row-card__text">
|
||||
<span className="row-card__sub">{ru.today.week}</span>
|
||||
<span className="row-card__title">{ru.today.weekTaken(taken, counted)}</span>
|
||||
</span>
|
||||
<Icon name="chevronRight" />
|
||||
</span>
|
||||
<WeekDots week={rx.week} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// C1 / C2 · Сегодня
|
||||
export function Today() {
|
||||
const t = ru.today;
|
||||
@@ -43,20 +64,27 @@ export function Today() {
|
||||
const takenDoses = plan?.doses.filter((d) => d.status === "taken") ?? [];
|
||||
const allDone = plan !== null && pendingDoses.length === 0 && plan.checkin_done && !hasOpenStep;
|
||||
const nextDose = plan?.prescriptions.flatMap((p) => p.times).sort()[0];
|
||||
const noDoctor = session?.mode === "anonymous" || (plan !== null && !plan.has_doctor);
|
||||
|
||||
const now = plan?.now ?? "00:00";
|
||||
const openDose = (d: TodayDose) => navigate(`/dose/${d.id}`);
|
||||
const visit = plan?.next_visit ? <VisitCard date={plan.next_visit} onOpen={() => navigate("/doctor")} /> : null;
|
||||
const hasDone = takenDoses.length > 0 || plan?.checkin_done;
|
||||
|
||||
return (
|
||||
<AppShell aside={visit}>
|
||||
<div className="stack">
|
||||
<header className="today-head">
|
||||
<div className="dash">
|
||||
<header className="today-head dash__head">
|
||||
<div>
|
||||
<h1 className="h1">{t.title}</h1>
|
||||
{plan && <p className="muted">{humanDate(plan.today)}</p>}
|
||||
</div>
|
||||
<button type="button" className="icon-btn icon-btn--bell" onClick={() => navigate("/messages")} aria-label={ru.nav.messages}>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-btn icon-btn--bell"
|
||||
onClick={() => navigate("/messages")}
|
||||
aria-label={ru.nav.messages}
|
||||
>
|
||||
<Icon name="bell" />
|
||||
{dialog.unread > 0 && <span className="dot" />}
|
||||
</button>
|
||||
@@ -64,113 +92,147 @@ export function Today() {
|
||||
|
||||
{failed && <Notice tone="danger">{ru.common.networkError}</Notice>}
|
||||
|
||||
{hasOpenStep && (
|
||||
<button type="button" className="task task--hero" onClick={() => navigate("/flow")}>
|
||||
<span className="task__icon">
|
||||
<Icon name="message" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.newMessage}</span>
|
||||
<span className="task__sub">{dialog.pending[0]?.text.split("\n")[0].slice(0, 80)}</span>
|
||||
</span>
|
||||
<span className="chip chip--teal-solid">{t.open}</span>
|
||||
</button>
|
||||
)}
|
||||
<section className="dash__main" aria-label={t.title}>
|
||||
{hasOpenStep && (
|
||||
<button type="button" className="task task--hero" onClick={() => navigate("/flow")}>
|
||||
<span className="task__icon">
|
||||
<Icon name="message" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.newMessage}</span>
|
||||
<span className="task__sub">{dialog.pending[0]?.text.split("\n")[0].slice(0, 90)}</span>
|
||||
</span>
|
||||
<span className="chip chip--teal-solid">{t.open}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{allDone ? (
|
||||
<section className="alldone">
|
||||
<span className="done__badge" aria-hidden="true">
|
||||
<Icon name="check" size={36} />
|
||||
</span>
|
||||
<h2 className="h2 center">{t.allDoneTitle}</h2>
|
||||
<p className="muted center">{nextDose ? t.allDoneNext(`завтра в ${nextDose}`) : t.allDoneNoMeds}</p>
|
||||
{plan && plan.prescriptions.length > 0 && (
|
||||
<button type="button" className="btn btn--primary" onClick={() => navigate("/meds")}>
|
||||
{t.viewMeds}
|
||||
<Icon name="chevronRight" size={20} />
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
) : (
|
||||
<div className="tasks">
|
||||
{pendingDoses.map((d) => (
|
||||
{allDone ? (
|
||||
<div className="alldone">
|
||||
<span className="done__badge" aria-hidden="true">
|
||||
<Icon name="check" size={36} />
|
||||
</span>
|
||||
<h2 className="h2 center">{t.allDoneTitle}</h2>
|
||||
<p className="muted center">{nextDose ? t.allDoneNext(`завтра в ${nextDose}`) : t.allDoneNoMeds}</p>
|
||||
{plan && plan.prescriptions.length > 0 && (
|
||||
<button type="button" className="btn btn--primary btn--auto" onClick={() => navigate("/meds")}>
|
||||
{t.viewMeds}
|
||||
<Icon name="chevronRight" size={20} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tasks">
|
||||
{pendingDoses.map((d) => (
|
||||
<button
|
||||
key={d.id}
|
||||
type="button"
|
||||
className={d.time <= now && !hasOpenStep ? "task task--hero" : "task"}
|
||||
onClick={() => openDose(d)}
|
||||
>
|
||||
<span className="task__icon task__icon--teal">
|
||||
<Icon name="pill" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.takeMed}</span>
|
||||
<span className="task__sub">
|
||||
{d.time} · {d.drug}, {d.dose}
|
||||
</span>
|
||||
</span>
|
||||
{d.time <= now && <span className="chip chip--teal-solid">{t.now}</span>}
|
||||
<Icon name="chevronRight" />
|
||||
</button>
|
||||
))}
|
||||
{plan && !plan.checkin_done && (
|
||||
<button
|
||||
type="button"
|
||||
className="task"
|
||||
onClick={() => go("menu:checkin", t.checkin)}
|
||||
disabled={dialog.busy}
|
||||
>
|
||||
<span className="task__icon task__icon--teal">
|
||||
<Icon name="smile" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.checkin}</span>
|
||||
<span className="task__sub">{t.checkinHint}</span>
|
||||
</span>
|
||||
<Icon name="chevronRight" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</section>
|
||||
|
||||
<section className="dash__side">
|
||||
{plan?.next_visit && (
|
||||
<div className="only-phone">
|
||||
<VisitCard date={plan.next_visit} onOpen={() => navigate("/doctor")} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!allDone && hasDone && (
|
||||
<div className="card">
|
||||
<h2 className="h3">{t.done}</h2>
|
||||
{takenDoses.map((d) => (
|
||||
<div key={d.id} className="done-row">
|
||||
<span className="badge-icon badge-icon--teal">
|
||||
<Icon name="check" size={16} />
|
||||
</span>
|
||||
<span>
|
||||
<span className="done-row__title">{t.medTaken}</span>
|
||||
<span className="done-row__sub">{t.medTakenAt(d.time)}</span>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{plan?.checkin_done && (
|
||||
<div className="done-row">
|
||||
<span className="badge-icon badge-icon--teal">
|
||||
<Icon name="check" size={16} />
|
||||
</span>
|
||||
<span className="done-row__title">{t.checkinDone}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plan?.prescriptions[0] && <WeekCard rx={plan.prescriptions[0]} onOpen={() => navigate("/meds")} />}
|
||||
|
||||
{noDoctor ? (
|
||||
<Notice>{t.noDoctor}</Notice>
|
||||
) : (
|
||||
<div className="quick">
|
||||
<button
|
||||
key={d.id}
|
||||
type="button"
|
||||
className={d.time <= now && !hasOpenStep ? "task task--hero" : "task"}
|
||||
onClick={() => openDose(d)}
|
||||
className="quick__btn"
|
||||
onClick={() => go("menu:med_question", t.medQuestion)}
|
||||
disabled={dialog.busy}
|
||||
>
|
||||
<span className="task__icon task__icon--teal">
|
||||
<Icon name="pill" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.takeMed}</span>
|
||||
<span className="task__sub">{d.time}</span>
|
||||
</span>
|
||||
{d.time <= now && <span className="chip chip--teal-solid">{t.now}</span>}
|
||||
<Icon name="chevronRight" />
|
||||
</button>
|
||||
))}
|
||||
{plan && !plan.checkin_done && (
|
||||
<button type="button" className="task" onClick={() => go("menu:checkin", t.checkin)} disabled={dialog.busy}>
|
||||
<span className="task__icon task__icon--teal">
|
||||
<Icon name="smile" />
|
||||
</span>
|
||||
<span className="task__text">
|
||||
<span className="task__title">{t.checkin}</span>
|
||||
<span className="task__sub">{t.checkinHint}</span>
|
||||
</span>
|
||||
<Icon name="chevronRight" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!allDone && (takenDoses.length > 0 || plan?.checkin_done) && (
|
||||
<section className="stack stack--sm">
|
||||
<h2 className="h3">{t.done}</h2>
|
||||
{takenDoses.map((d) => (
|
||||
<div key={d.id} className="done-row">
|
||||
<span className="badge-icon badge-icon--teal">
|
||||
<Icon name="check" size={16} />
|
||||
<span className="tile-icon tile-icon--blue">
|
||||
<Icon name="question" />
|
||||
</span>
|
||||
<span>
|
||||
<span className="done-row__title">{t.medTaken}</span>
|
||||
<span className="done-row__sub">{t.medTakenAt(d.time)}</span>
|
||||
<span className="quick__title">{t.medQuestion}</span>
|
||||
<span className="quick__sub">{t.medQuestionHint}</span>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{plan?.checkin_done && (
|
||||
<div className="done-row">
|
||||
<span className="badge-icon badge-icon--teal">
|
||||
<Icon name="check" size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="quick__btn"
|
||||
onClick={() => go("menu:contact_doctor", t.messageDoctor)}
|
||||
disabled={dialog.busy}
|
||||
>
|
||||
<span className="tile-icon tile-icon--blue">
|
||||
<Icon name="message" />
|
||||
</span>
|
||||
<span className="done-row__title">{t.checkinDone}</span>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{plan?.next_visit && (
|
||||
<div className="only-phone">
|
||||
<VisitCard date={plan.next_visit} onOpen={() => navigate("/doctor")} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{session?.mode === "anonymous" || (plan && !plan.has_doctor) ? (
|
||||
<Notice>{t.noDoctor}</Notice>
|
||||
) : (
|
||||
<div className="quick">
|
||||
<button type="button" className="quick__btn" onClick={() => go("menu:med_question", t.medQuestion)} disabled={dialog.busy}>
|
||||
<Icon name="question" />
|
||||
{t.medQuestion}
|
||||
</button>
|
||||
<button type="button" className="quick__btn" onClick={() => go("menu:contact_doctor", t.messageDoctor)} disabled={dialog.busy}>
|
||||
<Icon name="message" />
|
||||
{t.messageDoctor}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<span>
|
||||
<span className="quick__title">{t.messageDoctor}</span>
|
||||
<span className="quick__sub">{t.messageDoctorHint}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user