File size: 11,931 Bytes
f10d92f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
"""
Human+ Reusable UI Components
Prinsip refactor: gunakan Streamlit native sebanyak mungkin.
HTML hanya untuk elemen dekoratif (hero logo, gradient) yang
tidak ada padanan native-nya.
"""

import streamlit as st
from typing import Literal, Optional
from ui.theme import COLORS, BIOMARKER_STATUS, PROTOCOL_SECTIONS


# ════════════════════════════════════════════════
# HERO & NAVIGATION
# ════════════════════════════════════════════════

def render_hero(
    subtitle: str = "Upload hasil lab Anda β†’ Dapatkan rekomendasi IV drip & protocol personal"
):
    """
    Hero header β€” HTML dipertahankan hanya untuk gradient text logo
    yang tidak bisa dibuat dengan st.title() biasa.
    """
    st.markdown(
        """
        <div style="text-align:center; padding: 32px 0 8px;">
            <p style="
                font-size:0.72rem; font-weight:600;
                letter-spacing:0.2em; text-transform:uppercase;
                color:#4a4a6a; margin-bottom:6px;
            ">Human+ Bali Β· By Benjamin White</p>
            <h1 style="
                font-family:'DM Serif Display', Georgia, serif;
                font-size:2.6rem; font-weight:900; line-height:1;
                background: linear-gradient(135deg, #a78bfa, #38bdf8, #22d3ee);
                -webkit-background-clip: text; -webkit-text-fill-color: transparent;
                background-clip: text; margin:0 0 8px;
            ">Lab-to-Protocol</h1>
        </div>
        """,
        unsafe_allow_html=True,
    )
    st.caption(f"🧬 {subtitle}")
    st.write("")


def render_step_indicator(current_step: int):
    """
    3-step progress indicator menggunakan st.columns + native markdown.
    Tidak ada HTML kompleks β€” hanya emoji dan teks.
    """
    steps = ["πŸ“€ Upload", "πŸ”¬ Analisis", "πŸ“‹ Protocol"]
    cols = st.columns(3)

    for i, (col, label) in enumerate(zip(cols, steps), 1):
        with col:
            if i < current_step:
                # Done
                st.markdown(
                    f"<div class='hp-step done'>βœ“ {label}</div>",
                    unsafe_allow_html=True,
                )
            elif i == current_step:
                # Active
                st.markdown(
                    f"<div class='hp-step active'>β—‰ {label}</div>",
                    unsafe_allow_html=True,
                )
            else:
                # Pending
                st.markdown(
                    f"<div class='hp-step pending'>β—‹ {label}</div>",
                    unsafe_allow_html=True,
                )

    st.divider()


def render_trust_badges():
    """Trust badges menggunakan st.columns β€” tidak ada HTML."""
    col1, col2, col3, col4 = st.columns(4)
    badges = [
        ("πŸ”’", "Stateless"),
        ("πŸ—‘οΈ", "Tidak disimpan"),
        ("⚑", "Gemini AI"),
        ("🌿", "Human+ Bali"),
    ]
    for col, (icon, text) in zip([col1, col2, col3, col4], badges):
        with col:
            st.caption(f"{icon} {text}")
    st.write("")


def render_section_label(text: str, icon: str = ""):
    """
    Section label menggunakan st.markdown dengan minimal styling β€”
    hanya border-left yang tidak ada padanan native.
    """
    prefix = f"{icon} " if icon else ""
    st.markdown(
        f"<p style='font-size:0.7rem; font-weight:700; letter-spacing:0.14em; "
        f"color:#8b8ba7; border-left:2px solid #8b5cf6; padding-left:8px; "
        f"margin:20px 0 10px;'>{prefix}{text}</p>",
        unsafe_allow_html=True,
    )


# ════════════════════════════════════════════════
# LOADING STATES
# ════════════════════════════════════════════════

ANALYSIS_STEPS = [
    "Membaca dokumen PDF",
    "Mengidentifikasi biomarker",
    "Mencocokkan dengan range Human+",
    "Menyusun temuan utama",
]

PROTOCOL_STEPS = [
    "Mengambil knowledge base Human+",
    "Menyesuaikan dengan hasil lab Anda",
    "Menyusun rekomendasi IV drip",
    "Finalisasi protocol personal",
]


def render_loading_steps(steps: list[str], current_index: int):
    """
    Loading steps menggunakan st.markdown per baris β€” lebih stabil
    dari satu blok HTML besar.
    """
    for i, step in enumerate(steps):
        if i < current_index:
            icon, color = "βœ“", "#10b981"
        elif i == current_index:
            icon, color = "β—‰", "#8b5cf6"
        else:
            icon, color = "β—‹", "#4a4a6a"

        st.markdown(
            f"<p style='color:{color}; font-size:0.85rem; margin:4px 0;'>"
            f"{icon} &nbsp; {step}</p>",
            unsafe_allow_html=True,
        )


def render_progress_bar(value: float, label: str = ""):
    """Native st.progress β€” tidak butuh HTML sama sekali."""
    if label:
        st.caption(label)
    st.progress(value)


# ════════════════════════════════════════════════
# BIOMARKER COMPONENTS
# ════════════════════════════════════════════════

StatusType = Literal["optimal", "warning", "danger", "neutral"]

_STATUS_EMOJI = {
    "optimal": "🟒",
    "warning": "🟑",
    "danger":  "πŸ”΄",
    "neutral": "βšͺ",
}

_STATUS_COLOR = {
    "optimal": "#10b981",
    "warning": "#f59e0b",
    "danger":  "#ef4444",
    "neutral": "#6b7280",
}


def render_biomarker_row(
    name: str,
    value: str,
    unit: str,
    status: StatusType,
    reference: str = "",
):
    """
    Biomarker row menggunakan st.columns + st.metric.
    Jauh lebih stabil dari HTML div, metric punya built-in styling.
    Border kiri status tetap pakai satu baris HTML minimal.
    """
    color = _STATUS_COLOR.get(status, "#6b7280")
    emoji = _STATUS_EMOJI.get(status, "βšͺ")

    # Border kiri berwarna β€” satu-satunya HTML di sini
    st.markdown(
        f"<div style='border-left:3px solid {color}; padding-left:10px; margin:2px 0 0;'>",
        unsafe_allow_html=True,
    )

    col_name, col_val = st.columns([3, 1])
    with col_name:
        st.markdown(f"**{emoji} {name}**")
        if reference:
            st.caption(f"Target Human+: {reference}")
    with col_val:
        st.metric(
            label=unit,
            value=value,
            label_visibility="visible",
        )

    st.markdown("</div>", unsafe_allow_html=True)
    st.write("")


def render_biomarker_summary_banner(optimal: int, warning: int, danger: int):
    """
    Summary count banner menggunakan st.columns + st.metric native.
    Tidak ada HTML sama sekali.
    """
    total = optimal + warning + danger
    st.caption(f"πŸ”¬ RINGKASAN BIOMARKER β€” {total} MARKER DIANALISIS")

    col1, col2, col3 = st.columns(3)
    with col1:
        st.metric(label="πŸ”΄ Perlu Perhatian", value=str(danger))
    with col2:
        st.metric(label="🟑 Sub-Optimal", value=str(warning))
    with col3:
        st.metric(label="🟒 Optimal", value=str(optimal))

    st.write("")


def render_biomarker_chip(name: str, status: StatusType):
    """Inline chip menggunakan caption + emoji β€” tidak butuh HTML."""
    emoji = _STATUS_EMOJI.get(status, "βšͺ")
    st.caption(f"{emoji} {name}")


# ════════════════════════════════════════════════
# PROTOCOL COMPONENTS
# ════════════════════════════════════════════════

def render_protocol_section(
    section_key: str,
    content: str,
    custom_icon: str = "",
    custom_title: str = "",
    accent_color: str = "",
):
    """
    Protocol section menggunakan st.expander native sebagai container,
    dengan header yang jelas. Content di-render sebagai st.markdown biasa.
    """
    cfg = PROTOCOL_SECTIONS.get(section_key, {
        "icon": custom_icon or "πŸ“‹",
        "title": custom_title or "Section",
        "color": accent_color or COLORS["accent_violet"],
    })

    icon  = custom_icon  or cfg["icon"]
    title = custom_title or cfg["title"]

    with st.expander(f"{icon} {title}", expanded=True):
        if content and content.strip():
            st.markdown(content)
        else:
            st.caption("Tidak ada data untuk section ini.")


# ════════════════════════════════════════════════
# ERROR & EMPTY STATES
# ════════════════════════════════════════════════

def render_error_state(
    title: str = "Terjadi Kesalahan",
    message: str = "Silakan coba lagi.",
    retry_label: str = "Coba Lagi",
    show_contact: bool = False,
):
    """Error state menggunakan st.error native β€” selalu render dengan benar."""
    st.error(f"**{title}**\n\n{message}", icon="⚠️")
    if show_contact:
        st.caption("Butuh bantuan? Hubungi [@bennywhitethatsright](https://instagram.com/bennywhitethatsright)")


def render_empty_state(
    emoji: str = "🧬",
    title: str = "Belum ada data",
    message: str = "",
):
    """Empty state menggunakan st.info native."""
    st.info(f"{emoji} **{title}**\n\n{message}" if message else f"{emoji} **{title}**")


# ════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════

def render_footer():
    """Footer menggunakan st.divider + st.caption native."""
    st.divider()
    st.caption(
        "**FROM INJURED β†’ OPTIMIZED**  \n"
        "Human+ Bali Β· Didirikan oleh Benjamin White Β· "
        "[@bennywhitethatsright](https://instagram.com/bennywhitethatsright)  \n"
        "*Data Anda tidak disimpan. Aplikasi 100% stateless. "
        "Bukan pengganti konsultasi medis.*"
    )


# ════════════════════════════════════════════════
# UTILITY CARDS
# ════════════════════════════════════════════════

def render_info_card(title: str, body: str, icon: str = "ℹ️"):
    """
    Info card menggunakan st.info β€” native, selalu render.
    Body sebaiknya plain text, bukan HTML.
    """
    # Strip HTML tags sederhana dari body sebelum render
    import re
    clean_body = re.sub(r"<[^>]+>", "", body).strip()
    st.info(f"{icon} **{title}**\n\n{clean_body}")


def render_success_banner(title: str, subtitle: str = ""):
    """Success banner menggunakan st.success native."""
    text = f"**{title}**"
    if subtitle:
        text += f"\n\n{subtitle}"
    st.success(text, icon="βœ…")


def render_highlight_quote(text: str):
    """
    Motivational quote β€” satu-satunya komponen yang tetap pakai
    HTML minimal karena border-left gradient tidak ada di native Streamlit.
    """
    st.markdown(
        f"<blockquote style='"
        f"border-left:3px solid #8b5cf6; padding:12px 16px; margin:16px 0; "
        f"background:rgba(109,40,217,0.06); border-radius:0 8px 8px 0;'>"
        f"<em style='color:#c4c4e0;'>{text}</em><br>"
        f"<small style='color:#4a4a6a;'>β€” Benjamin White, Founder Human+ Bali</small>"
        f"</blockquote>",
        unsafe_allow_html=True,
    )