Files
co_app/app/pages/costobjects.py

153 lines
4.7 KiB
Python

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
st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide")
authenticator = require_login()
st.session_state["authenticator"] = authenticator
@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.sidebar.header("Filter")
if st.sidebar.button("Refresh (Global)"):
cache_data.clear()
st.rerun()
year = st.sidebar.selectbox(label="Jahr", options=(2025, 2026), index=1)
filter_text = st.sidebar.text_input(label="Textsuche", placeholder="Suche Objekt, Text, Verantwortlicher")
col_s1, col_s2 = st.sidebar.columns(2)
with col_s1:
typ = st.sidebar.selectbox(label="Typ", options=sorted(df["typ"].dropna().unique()), index=1)
with col_s2:
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.dataframe(df, hide_index=True, width="stretch", height="stretch")
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 = df.sort_values(by="obj", key=lambda s: pd.to_numeric(s, errors="coerce"))
df_clean = df[df.columns[:-1]] # letzte Spalten entfernen (sysdate)
df_display = df_clean.loc[mask]
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}")
st.text("Datenquelle: Oracle (PENTA)")
with col2:
st.markdown(f"<div style='text-align: right;'>Anzahl Zeilen: {row_count}</div>", unsafe_allow_html=True)
if __name__ == "__main__":
df = page()