""" Gedeelde UI-hulpmiddelen voor app.py en alle pages/*.py. Elke Streamlit-pagina draait als los script, dus gedeelde constanten/functies (kleurenpalet, thema-detectie) horen hier thuis in plaats van gedupliceerd te worden per pagina. """ from __future__ import annotations from datetime import date, datetime, timedelta, timezone import streamlit as st from nightscout_client import fetch_entries_range, fetch_treatments_range MMOL_HOVERTEMPLATE = "%{x|%d-%m %H:%M} · %{y:.1f} mmol/L" # Periode-opties voor de rapportagepagina's (FR1): elke pagina kiest zijn eigen # standaard (90 dagen voor statistiek/TIR, 14 dagen voor recente # glucosegrafieken) via `default_label`. PERIOD_OPTIONS_DAYS = {"14 dagen": 14, "30 dagen": 30, "90 dagen": 90} # Kleuren per rol, licht/donker — categorale kleuren voor identiteit # (glucose/insuline/carbs), status-kleuren (vast, nooit hergebruikt) voor # te laag/te hoog. PALETTE = { "light": { "glucose": "#2a78d6", "insulin": "#1baf7a", "carbs": "#eb6834", "low": "#d03b3b", "very_low": "#d03b3b", "high": "#fab219", "very_high": "#fab219", "band": "rgba(12,163,12,0.08)", "grid": "#e1e0d9", "muted": "#898781", "text": "#52514e", "good": "#0ca30c", }, "dark": { "glucose": "#3987e5", "insulin": "#199e70", "carbs": "#d95926", "low": "#d03b3b", "very_low": "#d03b3b", "high": "#fab219", "very_high": "#fab219", "band": "rgba(12,163,12,0.15)", "grid": "#2c2c2a", "muted": "#898781", "text": "#c3c2b7", "good": "#0ca30c", }, } def hex_to_rgba(hex_color: str, alpha: float) -> str: """Zet een '#rrggbb'-kleur om naar een 'rgba(r,g,b,a)'-string voor Plotly-fills.""" hex_color = hex_color.lstrip("#") r, g, b = (int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) return f"rgba({r},{g},{b},{alpha})" def get_colors() -> dict: """Geef de kleurenset terug die past bij het actieve Streamlit-thema.""" theme_type = "light" try: theme_type = st.context.theme.type or "light" except AttributeError: pass return PALETTE["dark" if theme_type == "dark" else "light"] def select_period_days(default_label: str) -> tuple[date, date, int]: """Periodekiezer voor rapportagepagina's; geeft (start, eind, aantal_dagen) terug.""" labels = list(PERIOD_OPTIONS_DAYS.keys()) label = st.radio("Periode", labels, index=labels.index(default_label), horizontal=True) days = PERIOD_OPTIONS_DAYS[label] end = datetime.now(tz=timezone.utc).date() start = end - timedelta(days=days - 1) return start, end, days @st.cache_data(ttl=3600, show_spinner="Data ophalen van Nightscout...") def load_range_data(start: date, end: date): """Gecachete Nightscout-fetch voor een vaste periode. Lange TTL (in tegenstelling tot de 60s van de live-pagina): historische data van een afgeronde periode verandert niet. """ glucose = fetch_entries_range(start, end) treatments = fetch_treatments_range(start, end) return glucose, treatments