import streamlit as st import pandas as pd #from data.scriptloader import get_sql from data.db import load_data from auth_runtime import require_login #from ui.sidebar import build_sidebar, hide_sidebar_if_logged_out #from auth import get_fullname_for_user import numpy as np from tools.excel_export import df_to_excel_bytes from tools.help_text import get_help st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide") authenticator = require_login() st.session_state["authenticator"] = authenticator st.markdown(""" """, unsafe_allow_html=True) description = get_help("costobjects") with st.expander(label="ℹ️ Hilfe / Hinweise", expanded=False): # ❓ st.markdown(description) @st.cache_data def cache_data() -> pd.DataFrame: """ Load and cache the base dataset for this page. Streamlit reruns the script on every interaction; caching avoids repeated I/O and makes filtering feel instant. """ try: df = load_data("ora_kostenobjekte", "oracle") return df except: st.warning("Fehler beim Laden der Daten", icon="⚠️") def sidebar_filters(df) -> dict: """ Render the sidebar UI and return the current filter selections. This function should contain UI concerns only (widgets, layout), and not data filtering logic, to keep the code maintainable. """ st.markdown(""" """, unsafe_allow_html=True) st.sidebar.header("Filter") if st.sidebar.button("Refresh (Global)"): cache_data.clear() st.rerun() filter_text = st.sidebar.text_input(label="Textsuche", placeholder="Suche Objekt, Text, Verantwortlicher") col_s1, col_s2 = st.sidebar.columns(2) with col_s1: year = st.selectbox(label="Jahr", options=(2025, 2026), index=1) with col_s2: typ = st.selectbox(label="Typ", options=sorted(df["typ"].dropna().unique()), index=1) obj = st.sidebar.multiselect("obj", sorted(df["obj"].dropna().unique())) zgrp1 = st.sidebar.multiselect("ZGrp1", sorted(df["zgrp1"].dropna().unique())) zgrp2 = st.sidebar.multiselect("ZGrp2", sorted(df["zgrp2"].dropna().unique())) zgrp3 = st.sidebar.multiselect("ZGrp3", sorted(df["zgrp3"].dropna().unique())) return {"year": year, "filter_text": filter_text, "typ": typ, "obj": obj, "zgrp1": zgrp1, "zgrp2": zgrp2, "zgrp3": zgrp3} def build_mask(df: pd.DataFrame, sidebar_filter: dict) -> np.ndarray: """ Build a boolean mask based on filter selections. The mask approach keeps the logic readable and makes it easy to add more conditions later. """ mask = np.ones(len(df), dtype=bool) filter_text = (sidebar_filter.get("filter_text") or "").strip() if filter_text: m1 = df["bezeichnung"].astype("string").str.contains(filter_text, case=False, na=False, regex=False) m2 = df["verantwortlicher"].astype("string").str.contains(filter_text, case=False, na=False, regex=False) m3 = df["vorgesetzter"].astype("string").str.contains(filter_text, case=False, na=False, regex=False) mask &= (m1 | m2 | m3) if sidebar_filter["year"]: mask &= df["jahr"].eq(sidebar_filter["year"]) if sidebar_filter["typ"]: mask &= df["typ"].eq(sidebar_filter["typ"]) if sidebar_filter["obj"]: mask &= df["obj"].isin(sidebar_filter["obj"]) if sidebar_filter["zgrp1"]: mask &= df["zgrp1"].isin(sidebar_filter["zgrp1"]) if sidebar_filter["zgrp2"]: mask &= df["zgrp2"].isin(sidebar_filter["zgrp2"]) if sidebar_filter["zgrp3"]: mask &= df["zgrp3"].isin(sidebar_filter["zgrp3"]) return mask def render_table(df): """ Render the result table. Keep this function presentation-only: it should not modify data. """ st.markdown("### Übersicht Kostenobjekte") st.dataframe(df, hide_index=True, width="stretch", height="stretch") def download_data(df): df.to_excel(path) def page(): """ Page entry point: orchestrates data loading, UI, filtering, and rendering. """ # ----------------------------- # Data loading (cached) # ----------------------------- df = cache_data() # ----------------------------- # UI (Sidebar) # ----------------------------- sidebar_filter = sidebar_filters(df) # ----------------------------- # Business logic (filtering) # ----------------------------- mask = build_mask(df, sidebar_filter) # ----------------------------- # Presentation # ----------------------------- df_clean = df[df.columns[:-1]] # letzte Spalten entfernen (sysdate) df_display = df_clean.loc[mask] df_display = df_display.sort_values(by="obj", key=lambda s: pd.to_numeric(s, errors="coerce")) render_table(df_display) col1, col2 = st.columns(2) data_as_of = df["sysdate"].max() row_count = len(df_display) with col1: st.text(f"Datenstand: {data_as_of}") with col2: st.markdown(f"