feat(dashboard): replace home with approved minimal design
CI / ruff · pytest (pull_request) Successful in 2m1s

Refs #73
This commit is contained in:
Pavel Eliseev
2026-10-04 14:54:14 +03:00
parent a2b1b2e7a5
commit c01bfc90df
8 changed files with 337 additions and 131 deletions
+119 -57
View File
@@ -23,21 +23,21 @@ class ToneSpec:
TONES: dict[str, ToneSpec] = {
"red": ToneSpec("#D64545", "#A32D2D", "#FCEBEB", "🔴", "Красная зона"),
"yellow": ToneSpec("#E0A800", "#7F5A00", "#FFF5D6", "🟡", "Жёлтая зона"),
"green": ToneSpec("#2E9E5B", "#1D6E3F", "#E6F4EC", "🟢", "Стабильно"),
"red": ToneSpec("#A83236", "#A83236", "#FAEEEE", "🔴", "Красная зона"),
"yellow": ToneSpec("#A37524", "#805714", "#FBF3E4", "🟡", "Жёлтая зона"),
"green": ToneSpec("#527F61", "#38634B", "#EEF4EE", "🟢", "Стабильно"),
"grey": ToneSpec("#9AA0A6", "#565C64", "#F0F1F3", "⚪", "Нет ответа"),
"blue": ToneSpec("#2A78D6", "#1F5DA8", "#E8F1FC", "🔵", ""),
}
INK = "#1B1F24"
INK_2 = "#4B535C"
INK_3 = "#7A828C"
INK = "#213B38"
INK_2 = "#526660"
INK_3 = "#63736F"
SURFACE = "#FFFFFF"
CANVAS = "#F6F7F9"
LINE = "#E3E6EA"
CANVAS = "#F6F7F3"
LINE = "#E1E7E3"
SERIES = TONES["blue"].solid
FONT_STACK = "'Inter', 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif"
FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif"
CSS = f"""
<style>
@@ -49,57 +49,99 @@ CSS = f"""
--grey: {TONES["grey"].solid}; --grey-ink: {TONES["grey"].ink}; --grey-tint: {TONES["grey"].tint};
--blue: {TONES["blue"].solid}; --blue-ink: {TONES["blue"].ink}; --blue-tint: {TONES["blue"].tint};
}}
.stApp {{ background: var(--canvas); }}
.block-container {{ padding-top: 2.2rem; max-width: 1320px; }}
.stApp {{ background: var(--canvas); color: var(--ink); font-family: {FONT_STACK}; color-scheme: light; }}
.block-container {{ padding: 2rem 3rem 2.5rem; max-width: 1440px; }}
[data-testid="stHeader"] {{ background: var(--canvas); }}
[data-testid="stSidebar"] {{ background: var(--surface); border-right: 1px solid var(--line); }}
[data-testid="stSidebarNavLink"] {{ border-radius: 8px; color: var(--ink-2); }}
[data-testid="stSidebarNavLink"][aria-current="page"] {{ background: #edf3ee; color: #285e4e; }}
[data-testid="stSidebarNavLink"] span, [data-testid="stSidebarNav"] span {{ color: inherit; }}
[data-testid="stSidebar"] [data-testid="stMarkdownContainer"] {{ color: var(--ink-2); }}
[data-testid="stWidgetLabel"], [data-testid="stCaptionContainer"] {{ color: var(--ink-2); }}
[data-testid="stTextInputRootElement"], [data-baseweb="select"] > div {{
background: var(--surface); border-color: var(--line); color: var(--ink); border-radius: 8px; }}
[data-testid="stApp"] input, [data-testid="stApp"] textarea {{ color: var(--ink); background: var(--surface); }}
[data-testid="stBaseButton-secondary"], [data-testid="stBaseButton-tertiary"] {{
background: var(--surface); color: var(--ink); border-color: var(--line); border-radius: 8px; }}
[data-testid="stBaseButton-primary"] {{ background: #285e4e; border-color: #285e4e; color: #fff; border-radius: 8px; }}
[data-testid="stBaseButton-primary"]:hover {{ background: #204b3e; border-color: #204b3e; color: #fff; }}
[data-testid="stBaseButton-secondary"]:hover {{ background: #edf3ee; border-color: #b4c6ba; color: #285e4e; }}
button:focus-visible, a:focus-visible, input:focus-visible, [tabindex="0"]:focus-visible {{
outline: 3px solid #598d7a !important; outline-offset: 3px; }}
.mt-sidebar-note {{ margin-top:36px; border-top:1px solid var(--line); padding-top:20px; color:var(--ink-3);
font-size:.78rem; line-height:1.6; }}
.mt-sidebar-note strong {{ display:block; font-weight:550; color:var(--ink); margin-bottom:5px; }}
.mt-workspace {{ display:flex; justify-content:space-between; align-items:center; gap:16px;
border-bottom:1px solid var(--line); padding-bottom:24px; margin-bottom:20px; color:var(--ink-3); font-size:.8rem; }}
.mt-doctor {{ display:flex; gap:10px; align-items:center; }}
.mt-doctor strong {{ display:block; color:var(--ink); font-weight:550; font-size:.8rem; }}
.mt-doctor small {{ display:block; font-size:.7rem; }}
.mt-doctor-icon {{ width:34px; height:34px; background:#e7ede5; color:#285e4e; border-radius:50%;
display:grid; place-items:center; font-size:22px; }}
h1, h2, h3 {{ color: var(--ink); letter-spacing: -0.01em; }}
/* ---------- page header ---------- */
.mt-head {{ display:flex; justify-content:space-between; align-items:flex-end; gap:16px; flex-wrap:wrap;
margin: 0 0 6px 0; }}
margin: 10px 0 12px 0; }}
.mt-head > div:first-child {{ flex: 1 1 420px; min-width: 0; }}
.mt-head .mt-side {{ flex: 0 1 320px; text-align: right; }}
.mt-head h1 {{ font-size: 1.75rem; font-weight: 700; margin: 0; padding: 0; line-height: 1.2; }}
.mt-sub {{ color: var(--ink-2); font-size: .95rem; margin-top: 4px; }}
.mt-kicker {{ color: var(--ink-3); font-size: .78rem; text-transform: uppercase; letter-spacing: .08em;
.mt-head h1 {{ font-size: 2.15rem; font-weight: 550; margin: 0; padding: 0; line-height: 1.2; }}
.mt-sub {{ color: var(--ink-2); font-size: .9rem; margin-top: 10px; }}
.mt-kicker {{ color: var(--ink-3); font-size: .68rem; text-transform: uppercase; letter-spacing: .13em;
margin-bottom:8px;
font-weight: 600; }}
/* ---------- KPI tiles ---------- */
.mt-kpis {{ display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin: 14px 0 6px; }}
.mt-kpi {{ background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px;
position: relative; overflow: hidden; }}
.mt-kpi::before {{ content:""; position:absolute; left:0; top:0; bottom:0; width:4px; background: var(--kpi-accent,
var(--line)); }}
.mt-kpi .lbl {{ color: var(--ink-2); font-size: .85rem; font-weight: 500; }}
.mt-kpi .val {{ color: var(--ink); font-size: 2rem; font-weight: 650; line-height: 1.15; margin-top: 4px; }}
.mt-kpi .hint {{ color: var(--ink-3); font-size: .78rem; margin-top: 2px; }}
/* ---------- patient rows ---------- */
.mt-cols, .mt-row {{ display:grid; align-items:center; gap: 14px;
grid-template-columns: minmax(0,2fr) minmax(0,1.5fr) 58px 58px minmax(0,1fr) minmax(0,1.2fr) minmax(0,2fr) 40px; }}
.mt-row > div {{ min-width: 0; overflow-wrap: anywhere; }}
.mt-cols {{ color: var(--ink-3); font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
font-weight: 600; padding: 0 0 0 21px; margin-top: 8px; }}
.mt-row {{ font-size: .9rem; color: var(--ink); }}
.mt-name {{ font-weight: 600; font-size: 1rem; display:flex; align-items:center; gap:8px; }}
.mt-meta {{ color: var(--ink-3); font-size: .8rem; margin-top: 3px; }}
.mt-dim {{ color: var(--ink-3); }}
.mt-num {{ font-variant-numeric: tabular-nums; font-weight: 600; }}
.mt-event {{ color: var(--ink-2); font-size: .85rem; line-height: 1.3; }}
.mt-dot {{ width:10px; height:10px; border-radius:50%; display:inline-block; flex: none; }}
div[class*="st-key-prow-"] {{ background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
padding: 10px 12px 10px 16px; border-left-width: 5px; transition: box-shadow .15s ease; }}
div[class*="st-key-prow-"]:hover {{ box-shadow: 0 2px 10px rgba(20,30,40,.08); }}
div[class*="st-key-prow-red"] {{ border-left-color: var(--red); background: linear-gradient(90deg, var(--red-tint) 0,
var(--surface) 22%); }}
div[class*="st-key-prow-yellow"] {{ border-left-color: var(--yellow); }}
div[class*="st-key-prow-green"] {{ border-left-color: var(--green); }}
div[class*="st-key-prow-grey"] {{ border-left-color: var(--grey); }}
/* ---------- overview and patient list ---------- */
.mt-kpis {{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); background:var(--surface);
border:1px solid var(--line); border-radius:12px; padding:22px 0; margin:10px 0 18px; }}
.mt-kpi {{ padding:0 24px; border-right:1px solid var(--line); min-width:0; }}
.mt-kpi:last-child {{ border-right:0; }}
.mt-kpi .lbl {{ color:var(--ink-2); font-size:.78rem; font-weight:500; }}
.mt-kpi .lbl::before {{ content:""; display:inline-block; width:6px; height:6px; border-radius:50%;
background:var(--kpi-accent,var(--line)); margin-right:7px; vertical-align:2px; }}
.mt-kpi .val {{ color:var(--ink); font-size:2rem; font-weight:550; line-height:1.3; margin-top:8px;
font-variant-numeric:tabular-nums; }}
.mt-kpi .hint {{ color:var(--ink-3); font-size:.72rem; margin-top:3px; }}
.mt-section-head {{ display:flex; justify-content:space-between; align-items:center; gap:16px; margin:14px 0 4px; }}
.mt-section-head h2 {{ font-size:1.25rem; font-weight:550; margin:0; padding:0; }}
.mt-section-head h2 span {{ background:#e6ece5; font-size:.75rem; padding:3px 7px; border-radius:5px;
vertical-align:middle; margin-left:6px; }}
.mt-section-head > span {{ font-size:.72rem; color:var(--ink-3); }}
.st-key-patient-list {{ background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:20px; }}
.st-key-patient-list [data-variant="segmented_control"] {{
color:var(--ink-2); background:var(--surface); border-color:var(--line); font-size:.8rem; }}
.st-key-patient-list [data-variant="segmented_control"][data-selected] {{
background:#edf3ee; color:#285e4e; border-color:#b4c6ba; font-weight:600; }}
.mt-cols, .mt-row {{ display:grid; align-items:center; gap:18px;
grid-template-columns:minmax(0,1.8fr) minmax(0,1.6fr) minmax(0,1fr) minmax(0,.8fr) minmax(0,1.2fr); }}
.mt-cols {{ color:var(--ink-3); font-size:.63rem; letter-spacing:.05em; text-transform:uppercase;
font-weight:550; padding:12px 0 4px; }}
.mt-row {{ color:var(--ink); font-size:.82rem; }}
.mt-row > div {{ min-width:0; overflow-wrap:anywhere; }}
.mt-name {{ font-weight:600; font-size:.92rem; }}
.mt-meta {{ color:var(--ink-3); font-size:.7rem; margin-top:4px; }}
.mt-num {{ font-variant-numeric:tabular-nums; font-weight:550; }}
.mt-dim {{ color:var(--ink-3); }}
.mt-dot {{ width:6px; height:6px; border-radius:50%; display:inline-block; flex:none; }}
.mt-scale-label {{ display:inline-block; min-width:42px; font-size:.67rem; color:var(--ink-3); margin-right:6px; }}
.mt-scores > div + div {{ margin-top:4px; }}
.mt-adherence .mt-bar {{ max-width:60px; height:3px; }}
.mt-mobile-label {{ display:none; }}
.mt-row-details {{ grid-column:1 / -1; display:grid; grid-template-columns:1fr 1fr 1fr; gap:20px;
font-size:.75rem; color:var(--ink-2); padding-top:12px; border-top:1px dashed var(--line); line-height:1.6; }}
.mt-field-label {{ display:block; color:var(--ink-3); font-size:.68rem; margin-bottom:4px; }}
div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4px; }}
.mt-list-footer {{ border-top:1px solid var(--line); padding-top:16px; margin-top:8px; display:flex;
justify-content:space-between; gap:12px; color:var(--ink-3); font-size:.72rem; }}
.mt-empty {{ text-align:center; padding:42px 20px; color:var(--ink-2); }}
.mt-empty strong {{ display:block; color:var(--ink); font-size:1rem; font-weight:550; }}
.mt-empty p {{ font-size:.85rem; margin:8px 0 0; }}
/* ---------- chips, badges, bars ---------- */
.mt-chip {{ display:inline-block; font-size:.72rem; padding: 1px 8px; border-radius: 999px; margin-right: 4px;
background: var(--blue-tint); color: var(--blue-ink); font-weight: 500; }}
.mt-badge {{ display:inline-flex; align-items:center; gap:6px; font-size:.8rem; font-weight:600; padding: 3px 10px;
border-radius: 999px; white-space: nowrap; }}
.mt-badge {{ display:inline-flex; align-items:center; gap:6px; font-size:.7rem; font-weight:550; padding: 5px 8px;
border-radius: 5px; white-space: normal; }}
.mt-badge.t-red {{ background: var(--red-tint); color: var(--red-ink); }}
.mt-badge.t-yellow {{ background: var(--yellow-tint); color: var(--yellow-ink); }}
.mt-badge.t-green {{ background: var(--green-tint); color: var(--green-ink); }}
@@ -110,7 +152,8 @@ div[class*="st-key-prow-grey"] {{ border-left-color: var(--grey); }}
.mt-count {{ display:inline-flex; min-width: 26px; height: 26px; border-radius: 999px; align-items:center;
justify-content:center; font-weight: 700; font-size: .85rem; }}
.mt-tip {{ position: relative; cursor: help; }}
.mt-tip:hover::after {{ content: attr(data-tip); position: absolute; z-index: 50; left: 0; top: 120%;
.mt-tip:hover::after, .mt-tip:focus-visible::after {{ content: attr(data-tip); position: absolute;
z-index: 50; left: 0; top: 120%;
min-width: 220px; max-width: 320px; white-space: pre-line; background: var(--ink); color: #fff;
font-size: .78rem; font-weight: 400; line-height: 1.4; padding: 8px 10px; border-radius: 8px;
box-shadow: 0 6px 20px rgba(0,0,0,.18); }}
@@ -118,13 +161,11 @@ div[class*="st-key-prow-grey"] {{ border-left-color: var(--grey); }}
/* ---------- banners & panels ---------- */
.mt-banner {{ border-radius: 12px; padding: 12px 16px; display:flex; gap: 12px; align-items:flex-start;
border: 1px solid; margin-bottom: 6px; }}
.mt-banner.t-red {{ background: var(--red-tint); border-color: #F2C4C4; color: var(--red-ink); }}
.mt-banner.t-red {{ background: #fffafa; border-color: #e7cbcb; border-left-width:3px; color: var(--red-ink); }}
.mt-banner.t-grey {{ background: var(--grey-tint); border-color: var(--line); color: var(--grey-ink); }}
.mt-banner b {{ color: inherit; }}
.mt-pulse {{ width: 12px; height: 12px; border-radius: 50%; background: var(--red); margin-top: 5px; flex: none;
box-shadow: 0 0 0 0 rgba(214,69,69,.6); animation: mtpulse 1.6s infinite; }}
@keyframes mtpulse {{ 0% {{ box-shadow: 0 0 0 0 rgba(214,69,69,.55); }} 70% {{ box-shadow: 0 0 0 10px
rgba(214,69,69,0); }} 100% {{ box-shadow: 0 0 0 0 rgba(214,69,69,0); }} }}
box-shadow: 0 0 0 4px #faeeee; }}
.mt-panel-title {{ font-size: .8rem; text-transform: uppercase; letter-spacing: .07em; font-weight: 650;
color: var(--ink-3); margin: 2px 0 8px; }}
.mt-alert {{ border-left: 4px solid var(--grey); padding: 4px 0 4px 12px; }}
@@ -160,10 +201,31 @@ div[class*="st-key-prow-grey"] {{ border-left-color: var(--grey); }}
.mt-footer {{ color: var(--ink-3); font-size: .78rem; text-align: center; margin: 36px 0 8px;
border-top: 1px solid var(--line); padding-top: 12px; }}
@media (max-width: 900px) {{
.mt-kpis {{ grid-template-columns: repeat(2, minmax(0,1fr)); }}
.mt-cols {{ display: none; }}
.mt-row {{ grid-template-columns: 1fr 1fr; }}
@media (max-width: 1100px) {{
.block-container {{ padding-left:1.5rem; padding-right:1.5rem; }}
.mt-kpi {{ padding:0 16px; }}
.mt-cols, .mt-row {{ gap:12px; }}
.mt-section-head > span {{ display:none; }}
}}
@media (max-width: 760px) {{
.block-container {{ padding:1.5rem 1rem; }}
.mt-workspace {{ flex-wrap:wrap; padding-bottom:18px; margin-bottom:10px; }}
.mt-head h1 {{ font-size:1.75rem; }}
.mt-kpis {{ grid-template-columns:repeat(2,minmax(0,1fr)); padding:0; }}
.mt-kpi {{ padding:18px; }}
.mt-kpi:nth-child(2) {{ border-right:0; }}
.mt-kpi:nth-child(-n+2) {{ border-bottom:1px solid var(--line); }}
.st-key-patient-list {{ padding:16px; }}
.mt-cols {{ display:none; }}
.mt-row {{ grid-template-columns:1fr 1fr; gap:16px; }}
.mt-person {{ grid-column:1 / -1; }}
.mt-mobile-label {{ display:block; color:var(--ink-3); font-size:.68rem; margin-bottom:5px; }}
.mt-row-details {{ grid-template-columns:1fr; gap:12px; }}
.mt-list-footer {{ flex-wrap:wrap; }}
.mt-tip:hover::after, .mt-tip:focus-visible::after {{ display:none; }}
}}
@media (prefers-reduced-motion: reduce) {{
*, *::before, *::after {{ animation:none !important; scroll-behavior:auto !important; transition:none !important; }}
}}
</style>
"""