BilancioCompetenze / src /streamlit_app.py
MatteoScript's picture
Update src/streamlit_app.py
25c892e verified
Raw
History Blame Contribute Delete
13.8 kB
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("<br>" * 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("<br>", 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",
)