import io import os import shutil import zipfile from pathlib import Path from typing import Dict, List, Optional, Tuple from dotenv import load_dotenv import streamlit as st from reporter.generate import generate_selected, list_people load_dotenv() st.set_page_config(page_title="BDC Report Generator", layout="wide") def _require_login() -> None: """Login semplice via variabili d'ambiente (file .env). Variabili attese: - AUTH_USER - AUTH_PASSWORD Se non configurate, l'app resta accessibile (utile in sviluppo). """ user = os.getenv("AUTH_USER", "") pwd = os.getenv("AUTH_PASSWORD", "") if not user or not pwd: return if st.session_state.get("auth_ok"): return st.markdown("
" * 3, unsafe_allow_html=True) _, col, _ = st.columns([1, 1.2, 1]) with col: st.title("Bilancio Competenze") u = st.text_input("Username", key="_login_u", placeholder="inserisci username") p = st.text_input("Password", type="password", key="_login_p", placeholder="inserisci password") st.markdown("
", unsafe_allow_html=True) if st.button("Accedi", type="primary", use_container_width=True): if u == user and p == pwd: st.session_state.auth_ok = True st.rerun() else: st.error("Credenziali non valide", icon="πŸ”’") st.stop() def _save_upload(upload, to_dir: Path) -> Optional[Path]: if upload is None: return None p = to_dir / upload.name p.write_bytes(upload.getbuffer()) return p def _zip_bytes(paths: List[Path]) -> bytes: buf = io.BytesIO() with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: for p in paths: if p.exists() and p.is_file(): z.write(p, arcname=p.name) return buf.getvalue() _require_login() make_pdf = True work_root = Path("/tmp/bdc_app") work_root.mkdir(parents=True, exist_ok=True) def _ensure_tmp() -> Path: if work_root.exists(): shutil.rmtree(work_root) work_root.mkdir(parents=True, exist_ok=True) return work_root def _load_people_lists( c_auto_p: Optional[Path], c_val_p: Optional[Path], m_auto_p: Optional[Path], m_val_p: Optional[Path], ) -> Tuple[List[str], List[str]]: collab_people: List[str] = [] manager_people: List[str] = [] if c_auto_p and c_val_p: collab_people = list_people(c_auto_p, c_val_p) if m_auto_p and m_val_p: manager_people = list_people(m_auto_p, m_val_p) return collab_people, manager_people # ── Sidebar ──────────────────────────────────────────────────────────────────── with st.sidebar: st.title("βš™οΈ Impostazioni") sezioni = st.multiselect( "Tipologia", options=["Collaboratori", "Manager"], default=["Collaboratori", "Manager"], ) show_collab = "Collaboratori" in sezioni show_manager = "Manager" in sezioni if show_collab: st.divider() st.title("πŸ‘₯ Collaboratori") tpl_collab = st.file_uploader("Modello Documento", type=["docx"], key="tpl_c") c_auto = st.file_uploader("Autovalutazione", type=["xlsx"], key="c_auto") c_val = st.file_uploader("Valutazione", type=["xlsx"], key="c_val") if all([tpl_collab, c_auto, c_val]): st.success("Files collaboratori inseriti", icon="βœ…") else: st.error("Inserire files collaboratori", icon="❌") else: tpl_collab = c_auto = c_val = None if show_manager: st.divider() st.title("🏒 Manager") tpl_manager = st.file_uploader("Modello Documento", type=["docx"], key="tpl_m") m_auto = st.file_uploader("Autovalutazione", type=["xlsx"], key="m_auto") m_val = st.file_uploader("Valutazione", type=["xlsx"], key="m_val") if all([tpl_manager, m_auto, m_val]): st.success("Files manager inseriti", icon="βœ…") else: st.error("Inserire files manager", icon="❌") else: tpl_manager = m_auto = m_val = None collab_ready = all([tpl_collab, c_auto, c_val]) manager_ready = all([tpl_manager, m_auto, m_val]) can_prepare = collab_ready or manager_ready st.divider() if st.button("Genera Report", type="primary", disabled=not can_prepare, use_container_width=True): tmp = _ensure_tmp() tpl_c_p = _save_upload(tpl_collab, tmp) if collab_ready else None tpl_m_p = _save_upload(tpl_manager, tmp) if manager_ready else None c_auto_p = _save_upload(c_auto, tmp) if collab_ready else None c_val_p = _save_upload(c_val, tmp) if collab_ready else None m_auto_p = _save_upload(m_auto, tmp) if manager_ready else None m_val_p = _save_upload(m_val, tmp) if manager_ready else None collab_people, manager_people = _load_people_lists(c_auto_p, c_val_p, m_auto_p, m_val_p) if not collab_people and not manager_people: st.error("Non ho trovato nessun nome nei file. Controlla la colonna 'Nome e cognome'.") else: st.session_state._paths = { "tpl_c": str(tpl_c_p) if tpl_c_p else "", "tpl_m": str(tpl_m_p) if tpl_m_p else "", "c_auto": str(c_auto_p) if c_auto_p else "", "c_val": str(c_val_p) if c_val_p else "", "m_auto": str(m_auto_p) if m_auto_p else "", "m_val": str(m_val_p) if m_val_p else "", "tmp": str(tmp), } st.session_state._collab_people = collab_people st.session_state._manager_people = manager_people st.session_state._phase = "select" st.session_state._results_ready = False st.rerun() # ── Main area ────────────────────────────────────────────────────────────────── st.title("πŸ“Š Bilancio Competenze") with st.expander("ℹ️ Funzionamento", expanded=True): st.markdown( """ Questa app genera report individuali di **Bilancio delle Competenze** in Word e PDF. ##### Istruzioni: 1. Nella barra laterale seleziona le sezioni da attivare (**Collaboratori**, **Manager**, o entrambe) 2. Carica il template Word e i due file Excel (autovalutazione e valutazione) per ciascuna sezione 3. Clicca **Genera Report** β€” scegli le persone da includere e scarica i report """ ) st.divider() phase = st.session_state.get("_phase") # ── Fase: selezione persone ──────────────────────────────────────────────────── if phase == "select": collab_people: List[str] = st.session_state.get("_collab_people", []) manager_people: List[str] = st.session_state.get("_manager_people", []) col_c, col_m = st.columns(2) sel_c: List[str] = [] sel_m: List[str] = [] with col_c: st.subheader(f"πŸ‘₯ Collaboratori") for n in collab_people: if st.checkbox(n, value=True, key=f"c_{n}"): sel_c.append(n) with col_m: st.subheader(f"🏒 Manager") for n in manager_people: if st.checkbox(n, value=True, key=f"m_{n}"): sel_m.append(n) tot = len(sel_c) + len(sel_m) c1, c2= st.columns([1, 1]) with c1: if st.button("Annulla", use_container_width=True): st.session_state._phase = None st.rerun() with c2: if st.button(f"Genera {tot} report", type="primary", disabled=tot == 0, use_container_width=True): st.session_state._selected_c = sel_c st.session_state._selected_m = sel_m st.session_state._phase = "generate" st.rerun() # ── Fase: generazione ───────────────────────────────────────────────────────── elif phase == "generate": sel_c = st.session_state.get("_selected_c", []) sel_m = st.session_state.get("_selected_m", []) total = len(sel_c) + len(sel_m) paths = st.session_state._paths def _p(key: str) -> Optional[Path]: v = paths.get(key, "") return Path(v) if v else None tpl_c_p, tpl_m_p = _p("tpl_c"), _p("tpl_m") c_auto_p, c_val_p = _p("c_auto"), _p("c_val") m_auto_p, m_val_p = _p("m_auto"), _p("m_val") out_dir = Path(paths["tmp"]) / "output" out_dir.mkdir(parents=True, exist_ok=True) header = st.empty() header.subheader(f"Generazione in corso… (0/{total})") progress = st.progress(0) log_area = st.empty() all_produced = [] all_warnings: List[str] = [] done = 0 log_lines: List[str] = [] for name in sel_c: log_lines.append(f"⏳ Collaboratore: **{name}**") log_area.markdown("\n\n".join(log_lines)) r = generate_selected( collab_auto=c_auto_p, collab_valut=c_val_p, collab_template=tpl_c_p, manager_auto=None, manager_valut=None, manager_template=None, selected_collaboratori=[name], selected_manager=[], output_dir=out_dir, make_pdf=make_pdf, ) all_produced.extend(r.produced) all_warnings.extend(r.warnings) done += 1 log_lines[-1] = f"βœ… Collaboratore: **{name}**" log_area.markdown("\n\n".join(log_lines)) progress.progress(done / total) header.subheader(f"Generazione in corso… ({done}/{total})") for name in sel_m: log_lines.append(f"⏳ Manager: **{name}**") log_area.markdown("\n\n".join(log_lines)) r = generate_selected( collab_auto=None, collab_valut=None, collab_template=None, manager_auto=m_auto_p, manager_valut=m_val_p, manager_template=tpl_m_p, selected_collaboratori=[], selected_manager=[name], output_dir=out_dir, make_pdf=make_pdf, ) all_produced.extend(r.produced) all_warnings.extend(r.warnings) done += 1 log_lines[-1] = f"βœ… Manager: **{name}**" log_area.markdown("\n\n".join(log_lines)) progress.progress(done / total) header.subheader(f"Generazione in corso… ({done}/{total})") header.subheader(f"Completato β€” {total} report generati βœ…") st.session_state._results = [ {"person": a.person, "kind": a.kind, "docx_path": a.docx_path, "pdf_path": a.pdf_path, "notes": a.notes} for a in all_produced ] st.session_state._result_warnings = all_warnings st.session_state._results_ready = True st.session_state._phase = "results" st.rerun() # ── Fase: risultati ──────────────────────────────────────────────────────────── elif phase == "results": results = st.session_state._results warnings = st.session_state.get("_result_warnings", []) if warnings: st.warning("\n".join(warnings)) st.success(f"Generazione completata: {len(results)} report", icon="βœ…") all_files: List[Path] = [] by_kind: Dict[str, list] = {} for a in sorted(results, key=lambda x: (x["kind"], x["person"])): by_kind.setdefault(a["kind"], []).append(a) def _render_table(artifacts: list) -> None: # Intestazione h0, h1, h2 = st.columns([4, 1, 1]) h0.markdown("**Persona**") h1.markdown("**Word**") h2.markdown("**PDF**") for a in artifacts: c0, c1, c2 = st.columns([4, 1, 1]) label = a["person"] if a["notes"]: label += f" \n*{a['notes']}*" c0.markdown(label) docx_path = Path(a["docx_path"]) all_files.append(docx_path) c1.download_button( "DOCX", data=docx_path.read_bytes(), file_name=docx_path.name, mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document", use_container_width=True, key=f"docx_{a['kind']}_{a['person']}", ) has_pdf = a["pdf_path"] and Path(a["pdf_path"]).exists() if has_pdf: pdf_path = Path(a["pdf_path"]) all_files.append(pdf_path) c2.download_button( "PDF", data=pdf_path.read_bytes(), file_name=pdf_path.name, mime="application/pdf", use_container_width=True, key=f"pdf_{a['kind']}_{a['person']}", type="primary", ) kinds = list(by_kind.keys()) if len(kinds) > 1: tabs = st.tabs(kinds) for tab, kind in zip(tabs, kinds): with tab: _render_table(by_kind[kind]) else: st.subheader(kinds[0]) _render_table(by_kind[kinds[0]]) st.divider() c1, c2= st.columns([1, 1]) with c1: if st.button("Annulla", use_container_width=True): st.session_state._phase = None st.rerun() with c2: st.download_button( "Scarica Tutti i Files", data=_zip_bytes(all_files), file_name="BDC_report_output.zip", mime="application/zip", use_container_width=True, key="zip_all", type="primary", )