"""Tätigkeitsnachweis-Generator – Streamlit App.""" import sys from pathlib import Path import streamlit as st # Projekt-Root zum Python-Path hinzufügen PROJECT_ROOT = Path(__file__).resolve().parent sys.path.insert(0, str(PROJECT_ROOT)) from src.config import ( load_settings, load_input_schema, load_output_mapping, get_available_input_files, get_available_templates, ) from src.models import AppConfig from src.parser import parse_input_file, group_by_employee, get_unique_antragsnummern, get_unique_employees from src.validator import validate_input_file, validate_template from src.writer import generate_activity_reports # --- Seite konfigurieren --- st.set_page_config( page_title="Tätigkeitsnachweis-Generator", page_icon="📋", layout="wide", ) st.title("📋 Tätigkeitsnachweis-Generator") st.markdown("Erstellt Tätigkeitsnachweise aus Input-Daten und einem Excel-Template.") # --- Konfiguration laden --- CONFIG_DIR = PROJECT_ROOT / "config" @st.cache_data def load_app_config(): config = load_settings(CONFIG_DIR, PROJECT_ROOT) config.input_schema = load_input_schema(CONFIG_DIR) config.output_mapping = load_output_mapping(CONFIG_DIR) return config try: app_config = load_app_config() except Exception as e: st.error(f"Fehler beim Laden der Konfiguration: {e}") st.stop() # --- Session State initialisieren --- if "parsed_records" not in st.session_state: st.session_state.parsed_records = [] if "input_validated" not in st.session_state: st.session_state.input_validated = False if "template_validated" not in st.session_state: st.session_state.template_validated = False # --- Layout: zwei Spalten --- col1, col2 = st.columns(2) with col1: st.subheader("1. Input-Datei") input_files = get_available_input_files(app_config) if not input_files: st.warning("Keine .xlsx-Dateien im input/-Ordner gefunden.") selected_input = None else: selected_input = st.selectbox( "Input-Datei wählen:", options=[""] + input_files, format_func=lambda x: "– Bitte wählen –" if x == "" else x, ) if selected_input: input_path = Path(app_config.input_dir) / selected_input # Validiere und parse sofort with st.spinner("Validiere Input-Datei…"): val_result = validate_input_file(input_path, app_config.input_schema) if not val_result.is_valid: for err in val_result.errors: st.error(err) st.session_state.input_validated = False else: for warn in val_result.warnings: st.warning(warn) # Parse die Datei records = parse_input_file( input_path, app_config.input_schema, app_config.month_sheet_pattern, app_config.skip_empty_name, app_config.skip_empty_antragsnummer, ) st.session_state.parsed_records = records st.session_state.input_validated = True # Zeige Zusammenfassung employees = get_unique_employees(records) antragsnummern = get_unique_antragsnummern(records) st.success(f"✅ {len(records)} Einträge in {val_result.info.get('sheets_with_data', 0)} Monats-Sheets") st.metric("Mitarbeiter", len(employees)) st.metric("Antragsnummern", len(antragsnummern)) with st.expander("Antragsnummern anzeigen"): for an in antragsnummern: st.text(f"• {an}") with col2: st.subheader("2. Template") template_files = get_available_templates(app_config) if not template_files: st.warning("Keine .xlsx-Dateien im template/-Ordner gefunden.") selected_template = None else: # Lock-Files herausfiltern real_templates = [t for t in template_files if not t.startswith("~")] if not real_templates: real_templates = template_files selected_template = st.selectbox( "Template wählen:", options=[""] + real_templates, format_func=lambda x: "– Bitte wählen –" if x == "" else x, ) if selected_template: template_path = Path(app_config.template_dir) / selected_template with st.spinner("Validiere Template…"): tpl_result = validate_template(template_path, app_config.output_mapping) if not tpl_result.is_valid: for err in tpl_result.errors: st.error(err) st.session_state.template_validated = False else: st.session_state.template_validated = True found = tpl_result.info.get("found_target_cells", 0) total = tpl_result.info.get("total_target_cells", 0) st.success(f"✅ {found}/{total} Zielfelder gefunden") if tpl_result.warnings: for warn in tpl_result.warnings: st.warning(warn) # --- Mitarbeiter-Auswahl --- st.subheader("3. Mitarbeiter auswählen") if st.session_state.input_validated and st.session_state.parsed_records: employees = get_unique_employees(st.session_state.parsed_records) selected_employees = st.multiselect( f"{len(employees)} Mitarbeiter gefunden – wähle aus, für wen Tätigkeitsnachweise erstellt werden sollen:", options=employees, default=employees, ) st.caption(f"{len(selected_employees)} von {len(employees)} Mitarbeitern ausgewählt") else: selected_employees = [] if not st.session_state.input_validated: st.info("Bitte zuerst eine gültige Input-Datei auswählen.") # --- Generate Button --- st.divider() gen_col1, gen_col2, gen_col3 = st.columns([1, 1, 2]) with gen_col1: generate_clicked = st.button( "🚀 Tätigkeitsnachweise generieren", type="primary", disabled=not (st.session_state.input_validated and st.session_state.template_validated and selected_employees), ) if generate_clicked: if not selected_employees: st.error("Keine Mitarbeiter ausgewählt.") else: # Filtere Records auf selektierte Mitarbeiter all_groups = group_by_employee(st.session_state.parsed_records) selected_groups = {name: all_groups[name] for name in selected_employees if name in all_groups} template_path = Path(app_config.template_dir) / selected_template output_dir = Path(app_config.output_dir) with st.spinner(f"Generiere {len(selected_groups)} Tätigkeitsnachweise…"): try: generated = generate_activity_reports( employee_records=selected_groups, template_path=template_path, mapping=app_config.output_mapping, output_dir=output_dir, filename_pattern=app_config.filename_pattern, ) st.success(f"✅ {len(generated)} Tätigkeitsnachweise erstellt:") for fpath in generated: st.text(f"📄 {fpath.name}") except Exception as e: st.error(f"Fehler bei der Generierung: {e}")