iscrizioni page

This commit is contained in:
Yan Xell
2026-05-13 12:52:29 +02:00
parent 35dfc4e55f
commit 2da668b3c8
7 changed files with 186 additions and 10 deletions
+13 -4
View File
@@ -6,8 +6,9 @@ from .queries import pec as q_pec
from .queries import logs as q_logs
from .queries import report as q_report
from .queries import documenti as q_documenti
from .queries import iscrizioni as q_iscrizioni
from .queries import email as q_email
from .queries import iscrizioni as q_iscrizioni
from .queries import api_iscrizioni as q_api_iscrizioni
from .queries import email as q_email
def _load_data(db_path: str) -> dict:
@@ -58,7 +59,7 @@ def _page_dashboard(db_path: str) -> str:
)
section_middle = (
'<section>'
'<h2><a href="/iscrizioni" class="sec-link">&#128203; Corsi Intraziendali</a></h2>'
'<h2><a href="/steps" class="sec-link">&#128203; RPA Steps</a></h2>'
f'<div class="grid">{cards_middle}</div>'
'</section>'
'<section>'
@@ -175,7 +176,15 @@ def _page_iscrizioni(db_path: str) -> str:
content = _tmpl('iscrizioni.html').substitute(
tbl_iscrizioni=q_iscrizioni.render_table(rows),
)
return _base('Corsi Intraziendali', content, 'nav_iscrizioni', db_path, _page_css('iscrizioni'), _page_js('iscrizioni'))
return _base('RPA Steps', content, 'nav_iscrizioni', db_path, _page_css('iscrizioni'), _page_js('iscrizioni'))
def _page_api_iscrizioni(db_path: str) -> str:
rows = q_api_iscrizioni.get_iscrizioni(db_path)
content = _tmpl('api_iscrizioni.html').substitute(
tbl_api_iscrizioni=q_api_iscrizioni.render_table(rows),
)
return _base('Iscrizioni', content, 'nav_api_iscrizioni', db_path, _page_css('api_iscrizioni'), _page_js('api_iscrizioni'))
def _page_email(db_path: str) -> str:
+88
View File
@@ -0,0 +1,88 @@
"""queries/api_iscrizioni.py — rpa_intra_api_iscrizione (Intraz)"""
from .db import query, count, e, dt, badge
def get_iscrizioni(db_path: str) -> list:
return query(db_path, """
SELECT
i.id,
i.rpa_process_id,
m.ragione_sociale AS sorgente,
i.sessione_id,
i.iscritto_nome,
i.iscritto_cognome,
i.iscritto_azienda,
i.iscritto_cf,
i.odv_numero,
i.stato_iscrizione,
i.stato_odv,
i.persona_inserita_only1,
i.note,
i.created_at,
i.updated_at
FROM rpa_intra_api_iscrizione i
LEFT JOIN rpa_mapping_aziende m ON m.id = i.azienda_sorgente_id
ORDER BY i.id DESC
LIMIT 500
""")
def get_stats(db_path: str) -> dict:
return {
'api_isc_total': count(db_path, "SELECT COUNT(*) AS n FROM rpa_intra_api_iscrizione"),
'api_isc_ok': count(db_path, "SELECT COUNT(*) AS n FROM rpa_intra_api_iscrizione WHERE stato_iscrizione='ok' AND stato_odv='ok'"),
'api_isc_errore': count(db_path, "SELECT COUNT(*) AS n FROM rpa_intra_api_iscrizione WHERE stato_iscrizione='errore' OR stato_odv='errore'"),
'api_isc_pending': count(db_path, "SELECT COUNT(*) AS n FROM rpa_intra_api_iscrizione WHERE stato_iscrizione='pending' OR stato_odv='pending'"),
}
def _stato_row(r: dict) -> str:
si, so = r.get('stato_iscrizione'), r.get('stato_odv')
if si == 'errore' or so == 'errore':
return 'errore'
if si == 'ok' and so == 'ok':
return 'ok'
return 'pending'
def _badge_stato(v: str) -> str:
if v == 'ok': return badge('ok', 'ok')
if v == 'errore': return badge('err', 'errore')
return badge('warn', 'pending')
def render_table(rows: list) -> str:
if not rows:
return '<p class="empty">Nessuna iscrizione trovata.</p>'
trs = ''.join(
f'<tr data-stato="{_stato_row(r)}"'
f' data-sorgente="{e(r.get("sorgente") or "")}"'
f' data-sessione="{e(r.get("sessione_id") or "")}"'
f' data-iscrizione="{e(r.get("stato_iscrizione") or "")}"'
f' data-odv="{e(r.get("stato_odv") or "")}"'
f'>'
f'<td>{e(r.get("id"))}</td>'
f'<td>{e(r.get("rpa_process_id"))}</td>'
f'<td>{e(r.get("sorgente"))}</td>'
f'<td>{e(r.get("sessione_id"))}</td>'
f'<td>{e(r.get("iscritto_cognome"))} {e(r.get("iscritto_nome"))}</td>'
f'<td>{e(r.get("iscritto_azienda"))}</td>'
f'<td>{e(r.get("iscritto_cf"))}</td>'
f'<td>{e(r.get("odv_numero"))}</td>'
f'<td>{"&#10003;" if r.get("persona_inserita_only1") else ""}</td>'
f'<td>{_badge_stato(r.get("stato_iscrizione"))}</td>'
f'<td>{_badge_stato(r.get("stato_odv"))}</td>'
f'<td class="nc">{e(r.get("note"))}</td>'
f'<td>{dt(r.get("updated_at"))}</td>'
f'</tr>'
for r in rows
)
return (
'<table><thead><tr>'
'<th>#</th><th>Run</th><th>Sorgente</th><th>Sessione</th>'
'<th>Iscritto</th><th>Azienda</th><th>CF</th><th>ODV N.</th>'
'<th>Nuova</th><th>Iscrizione</th><th>ODV</th><th>Note</th><th>Aggiornato</th>'
f'</tr></thead><tbody>{trs}</tbody></table>'
)
+3 -2
View File
@@ -55,10 +55,11 @@ def _base(title: str, content: str, active: str, db_path: str, page_css: str = '
if db_type == DbType.INTRAZ:
cls = 'active' if active == 'nav_iscrizioni' else ''
nav_step_link = f'<a href="/iscrizioni" class="{cls}">Corsi Intraziendali</a>'
nav_step_link = f'<a href="/steps" class="{cls}">RPA Steps</a>'
cls_api_isc = 'active' if active == 'nav_api_iscrizioni' else ''
nav_report_link = f'<a href="/iscrizioni-api" class="{cls_api_isc}">Iscrizioni</a>'
cls_email = 'active' if active == 'nav_email' else ''
nav_pec_link = f'<a href="/email" class="{cls_email}">Email</a>'
nav_report_link = ''
else:
cls = 'active' if active == 'nav_documenti' else ''
nav_step_link = f'<a href="/documenti" class="{cls}">Documenti</a>'
+6 -4
View File
@@ -8,7 +8,7 @@ from .config import _state, _access_log, DbType
from .db_finder import _find_db, _pick_db_file
from .pages import (
_page_dashboard, _page_runs, _page_pec, _page_logs,
_page_report, _page_documenti, _page_iscrizioni, _page_email, _page_server_logs,
_page_report, _page_documenti, _page_iscrizioni, _page_api_iscrizioni, _page_email, _page_server_logs,
)
from .queries.db import e as _e, query as _query, detect_db as _detect_db
from .queries import documenti as q_documenti
@@ -113,8 +113,9 @@ def make_handler(db_path: str):
'/server-logs': lambda: _page_server_logs(db_path),
}
if db_type == DbType.INTRAZ:
routes['/iscrizioni'] = lambda: _page_iscrizioni(db_path)
routes['/email'] = lambda: _page_email(db_path)
routes['/steps'] = lambda: _page_iscrizioni(db_path)
routes['/iscrizioni-api'] = lambda: _page_api_iscrizioni(db_path)
routes['/email'] = lambda: _page_email(db_path)
else:
routes['/report'] = lambda: _page_report(db_path)
routes['/documenti'] = lambda: _page_documenti(db_path)
@@ -143,7 +144,8 @@ def run_server(db_path: str, host: str = 'localhost', port: int = 8080):
print(f" Dashboard : http://localhost:{port}/")
print(f" Processi : http://localhost:{port}/runs")
if db_type == DbType.INTRAZ:
print(f" Iscrizioni : http://localhost:{port}/iscrizioni")
print(f" RPA Steps : http://localhost:{port}/steps")
print(f" Iscr. API : http://localhost:{port}/iscrizioni-api")
print(f" Email : http://localhost:{port}/email")
else:
print(f" PEC : http://localhost:{port}/pec")
+16
View File
@@ -0,0 +1,16 @@
(function(){
function applyFilter() {
var active = {};
document.querySelectorAll('.api-isc-filter input[type="checkbox"]').forEach(function(chk) {
active[chk.getAttribute('data-filter-stato')] = chk.checked;
});
document.querySelectorAll('tbody tr[data-stato]').forEach(function(tr) {
var stato = tr.getAttribute('data-stato');
var show = (stato in active) ? active[stato] : true;
tr.style.display = show ? '' : 'none';
});
}
document.querySelectorAll('.api-isc-filter input[type="checkbox"]').forEach(function(chk) {
chk.addEventListener('change', applyFilter);
});
})();
+36
View File
@@ -0,0 +1,36 @@
/* api_iscrizioni.css */
.api-isc-header {
display: flex;
align-items: center;
gap: 1.5rem;
flex-wrap: wrap;
margin-bottom: .5rem;
}
.api-isc-header h2 { margin: 0; }
.api-isc-filter {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.api-isc-filter .filter-group-label {
font-size: .85rem;
color: #64748b;
font-weight: 600;
}
.api-isc-filter label {
display: flex;
align-items: center;
gap: .35rem;
font-size: .9rem;
cursor: pointer;
user-select: none;
}
.api-isc-filter input[type="checkbox"] {
width: 1rem;
height: 1rem;
cursor: pointer;
accent-color: #2563eb;
}
+24
View File
@@ -0,0 +1,24 @@
<section class="page-header">
<div class="page-filter-wrap">
<div class="api-isc-header">
<h2>Iscrizioni (ultime 500)</h2>
<button id="btn-toggle-filters" class="btn-toggle-filters" title="Mostra/nascondi filtri">
<svg id="filters-arrow" class="filters-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>
<span class="filters-btn-text">Filtri</span>
</button>
</div>
<div id="filters-collapsible" class="filters-collapsible">
<div class="filters-collapsible-inner">
<div class="api-isc-filter">
<span class="filter-group-label">Stato riga:</span>
<label><input type="checkbox" data-filter-stato="ok" checked> OK</label>
<label><input type="checkbox" data-filter-stato="pending" checked> Pending</label>
<label><input type="checkbox" data-filter-stato="errore" checked> Errore</label>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="tw">${tbl_api_iscrizioni}</div>
</section>