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",
)