feat(web): dashboard ergonomic cu tab-uri, stepper import si microcopy uman (3.4)
Reorganizeaza interfata web pe trei principii, fara a atinge backend-ul de trimitere (worker, mapping, idempotency, masina de stari neatinse): - US-001 app/web/labels.py: modul pur stari tehnice -> text uman + clasa CSS - US-002 bara status /_fragments/status: microcopy uman, defalcare blocate, scoped cont - US-003 shell 6 tab-uri (Acasa/Import/Coada/Mapari/Cont/Nomenclator): deep-link ?tab=, panou activ randat server-side, fragmente inactive lazy, ARIA real - US-004 stepper import 4 pasi (pur vizual; hx-target + csrf pastrate) - US-005 Acasa onboarding checklist auto-bifat + colaps + empty states prietenoase Reparat in cursul VERIFY/CLOSE: izolare teste (reset ratelimit._hits in fixturi), regresie avertisment "cont in asteptare de activare" (re-introdus in bara status), culori hardcodate -> variabile paleta. 434 teste pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
app/web/templates/_acasa.html
Normal file
81
app/web/templates/_acasa.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<div id="acasa-section">
|
||||
|
||||
{% set toti_esentiali = are_creds and are_trimiteri %}
|
||||
|
||||
{% if toti_esentiali %}
|
||||
{# Ghid colapsat/discret cand toti pasii esentiali sunt gata #}
|
||||
<div class="ghid-complet" style="margin-bottom:12px; font-size:13px; color:var(--muted);">
|
||||
Totul e configurat —
|
||||
<a href="/?tab=coada">vezi coada</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Card ghid de pornire vizibil cand nu toti pasii sunt finalizati #}
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<h2 style="font-size:15px; margin:0 0 12px;">Primii pasi</h2>
|
||||
<ul style="list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:8px;">
|
||||
|
||||
{# Pas 1: Conecteaza contul RAR (esential) #}
|
||||
<li style="display:flex; align-items:flex-start; gap:10px;">
|
||||
{% if are_creds %}
|
||||
<span class="pas-bifat" style="color:var(--ok); font-weight:bold; flex-shrink:0;">✓</span>
|
||||
{% else %}
|
||||
<span class="pas-nebifat" style="color:var(--muted); flex-shrink:0;">○</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
<a href="/?tab=cont">Conecteaza-ti contul RAR</a>
|
||||
<span class="muted" style="font-size:12px; display:block;">
|
||||
Email + parola portal AUTOPASS RAR
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{# Pas 2: Cheie API (optional) #}
|
||||
<li style="display:flex; align-items:flex-start; gap:10px;">
|
||||
{% if are_cheie_folosita %}
|
||||
<span class="pas-bifat" style="color:var(--ok); font-weight:bold; flex-shrink:0;">✓</span>
|
||||
{% else %}
|
||||
<span class="pas-nebifat" style="color:var(--muted); flex-shrink:0;">○</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
<a href="/?tab=cont">Ia-ti cheia API</a>
|
||||
<span class="muted" style="font-size:12px; display:block;">
|
||||
<em>Optional</em> — doar daca trimiti din soft propriu prin API
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{# Pas 3: Import primul fisier (esential) #}
|
||||
<li style="display:flex; align-items:flex-start; gap:10px;">
|
||||
{% if are_trimiteri %}
|
||||
<span class="pas-bifat" style="color:var(--ok); font-weight:bold; flex-shrink:0;">✓</span>
|
||||
{% else %}
|
||||
<span class="pas-nebifat" style="color:var(--muted); flex-shrink:0;">○</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
<a href="/?tab=import">Importa primul fisier</a>
|
||||
<span class="muted" style="font-size:12px; display:block;">
|
||||
Incarca un fisier xlsx/csv cu prezentarile de declarat la RAR
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Rezumat si scurtaturi rapide (mereu vizibile) #}
|
||||
<div class="card">
|
||||
<h2 style="font-size:15px; margin:0 0 8px;">Bun venit la Gateway RAR AUTOPASS</h2>
|
||||
<p class="muted" style="margin:0 0 10px; font-size:13px;">
|
||||
Importa fisiere din tab-ul <strong><a href="/?tab=import">Import</a></strong>,
|
||||
urmareste coada in tab-ul <strong><a href="/?tab=coada">Coada</a></strong>
|
||||
si rezolva mapari lipsa in tab-ul <strong><a href="/?tab=mapari">Mapari</a></strong>.
|
||||
</p>
|
||||
<div style="display:flex; gap:12px; flex-wrap:wrap; font-size:13px;">
|
||||
<a href="/?tab=coada" class="cardlink">Coada submissions</a>
|
||||
<a href="/?tab=import" class="cardlink">Import fisier nou</a>
|
||||
<a href="/?tab=mapari" class="cardlink">Mapari operatii</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
14
app/web/templates/_coada.html
Normal file
14
app/web/templates/_coada.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<div id="coada-section">
|
||||
<div class="card">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 12px;">
|
||||
<h2 style="font-size:15px; margin:0;">Coada submissions</h2>
|
||||
<span style="margin-left:auto; display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<a class="cardlink" href="/v1/audit/export?status=sent" download>export CSV: trimise</a>
|
||||
<a class="cardlink" href="/v1/audit/export?status=all" download>toate</a>
|
||||
</span>
|
||||
</div>
|
||||
<div hx-get="/_fragments/submissions" hx-trigger="load, every 10s" hx-swap="innerHTML">
|
||||
<div class="empty">se incarca…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,7 +6,10 @@
|
||||
{% endif %}
|
||||
|
||||
{% if not pending %}
|
||||
<div class="empty">Nicio operatie nemapata. Tot ce a venit s-a tradus in coduri RAR.</div>
|
||||
<div class="empty">
|
||||
Nicio operatie nemapata — tot ce a venit s-a tradus in coduri RAR.
|
||||
<a href="/?tab=import">Importa un fisier nou</a> daca vrei sa adaugi prezentari.
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted" style="margin:0 0 12px; font-size:13px;">
|
||||
Operatii ROAAUTO necunoscute, blocate in <span class="s-needs_mapping">needs_mapping</span>.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="import-section">
|
||||
{% set pas = 2 %}{% include '_stepper.html' %}
|
||||
<div class="card">
|
||||
<h2 style="font-size:15px; margin:0 0 12px;">
|
||||
Mapare coloane —
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="import-section">
|
||||
{% set pas = 3 %}{% include '_stepper.html' %}
|
||||
<div class="card">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:12px;">
|
||||
<h2 style="font-size:15px; margin:0;">
|
||||
|
||||
80
app/web/templates/_status.html
Normal file
80
app/web/templates/_status.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<div id="status-bar" class="status-bar card"
|
||||
hx-get="/_fragments/status"
|
||||
hx-trigger="every 15s"
|
||||
hx-swap="outerHTML">
|
||||
|
||||
<!-- Cont in asteptare de activare (regasit din vechiul _banner; mereu vizibil) -->
|
||||
{% if not account_active %}
|
||||
<div style="margin-bottom:12px; padding:8px 10px; border-left:3px solid var(--warn);
|
||||
background:#201c0f; border-radius:6px; font-size:13px;">
|
||||
<strong>Cont in asteptare de activare.</strong>
|
||||
Configureaza credentialele RAR si pregateste importul acum; trimiterea catre RAR
|
||||
porneste automat dupa activare de catre administrator.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:flex; gap:24px; flex-wrap:wrap; align-items:flex-start;">
|
||||
|
||||
<!-- Starea worker (Trimitere automata) -->
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">{{ worker_lbl[0] }}</div>
|
||||
<div class="{{ worker_lbl[2] }}" title="{{ worker_lbl[1] }}">
|
||||
{{ worker_lbl[0].split(':')[1].strip() if ':' in worker_lbl[0] else worker_lbl[0] }}
|
||||
</div>
|
||||
{% if worker_lbl[1] %}
|
||||
<div class="muted" style="font-size:11px; max-width:220px;">{{ worker_lbl[1] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Legatura RAR -->
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">Legatura RAR</div>
|
||||
<div class="{{ rar_lbl[2] }}" title="{{ rar_lbl[1] }}">
|
||||
{{ rar_lbl[0].split(':')[1].strip() if ':' in rar_lbl[0] else rar_lbl[0] }}
|
||||
</div>
|
||||
{% if rar_lbl[1] and rar_lbl[2] != 's-sent' %}
|
||||
<div class="muted" style="font-size:11px; max-width:220px;">{{ rar_lbl[1] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Ultima autentificare RAR -->
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">{{ eticheta_ultima_auth }}</div>
|
||||
<div>{{ last_login or '—' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- In asteptare (queued) -->
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">In asteptare</div>
|
||||
<div class="s-queued">{{ counts_queued }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Declarate la RAR (sent) -->
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">Declarate la RAR</div>
|
||||
<div class="s-sent">{{ counts_sent }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Defalcare blocate pe motiv (doar daca exista) -->
|
||||
{% if blocate_defalcat %}
|
||||
<div style="margin-top:12px; padding-top:10px; border-top:1px solid var(--line);">
|
||||
<div style="font-size:12px; font-weight:600; margin-bottom:6px;">Necesita atentia ta</div>
|
||||
<div style="display:flex; gap:16px; flex-wrap:wrap;">
|
||||
{% for eticheta, n in blocate_defalcat %}
|
||||
{% if n > 0 %}
|
||||
<div>
|
||||
<span class="{{ eticheta[2] }}" style="font-size:13px;">{{ eticheta[0] }}</span>
|
||||
<span class="muted" style="font-size:12px; margin-left:4px;">({{ n }})</span>
|
||||
{% if eticheta[1] %}
|
||||
<div class="muted" style="font-size:11px; max-width:200px;">{{ eticheta[1] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
96
app/web/templates/_stepper.html
Normal file
96
app/web/templates/_stepper.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{#
|
||||
_stepper.html — Antet wizard import (PUR vizual, fara logica de rutare).
|
||||
|
||||
Parametru: `pas` (integer 1-4) — pasul curent.
|
||||
Utilizare in template-uri care mostenesc contextul Jinja2:
|
||||
{% set pas = 1 %}{% include '_stepper.html' %}
|
||||
sau cu `with`:
|
||||
{% with pas=2 %}{% include '_stepper.html' %}{% endwith %}
|
||||
|
||||
Cei 4 pasi ficsi:
|
||||
1. Incarca fisier
|
||||
2. Potriveste coloanele
|
||||
3. Verifica
|
||||
4. Confirma trimiterea
|
||||
|
||||
Stari vizuale:
|
||||
- index < pas → "facut" (bulina plina, text bifat)
|
||||
- index == pas → "activ" (evidentiat, aria-current="step")
|
||||
- index > pas → "viitor" (estompat)
|
||||
#}
|
||||
{%- set _pasi_import = [
|
||||
(1, "Incarca fisier", "Trage un fisier xlsx/csv aici sau foloseste butonul de alegere."),
|
||||
(2, "Potriveste coloanele", "Spune-ne ce coloana din fisier corespunde cu ce camp RAR."),
|
||||
(3, "Verifica", "Verifica randurile inainte sa le trimiti la RAR."),
|
||||
(4, "Confirma trimiterea", "Confirma numarul de prezentari — actiunea e ireversibila."),
|
||||
] -%}
|
||||
<nav class="stepper-import" aria-label="Pasii importului" style="
|
||||
display:flex;
|
||||
gap:0;
|
||||
align-items:stretch;
|
||||
margin-bottom:20px;
|
||||
border:1px solid var(--line);
|
||||
border-radius:8px;
|
||||
overflow:hidden;
|
||||
background:var(--card);
|
||||
">
|
||||
{% for nr, titlu, ajutor in _pasi_import %}
|
||||
{%- if nr < pas %}
|
||||
{%- set cls = "facut" -%}
|
||||
{%- set aria = "" -%}
|
||||
{%- elif nr == pas %}
|
||||
{%- set cls = "activ" -%}
|
||||
{%- set aria = ' aria-current="step"' -%}
|
||||
{%- else %}
|
||||
{%- set cls = "viitor" -%}
|
||||
{%- set aria = "" -%}
|
||||
{%- endif %}
|
||||
<div class="stepper-pas stepper-pas--{{ cls }}"{{ aria | safe }}
|
||||
style="
|
||||
flex:1;
|
||||
padding:10px 14px;
|
||||
border-right:{% if not loop.last %}1px solid var(--line){% else %}none{% endif %};
|
||||
{% if cls == 'activ' %}
|
||||
background:rgba(91,141,239,.10);
|
||||
{% elif cls == 'facut' %}
|
||||
opacity:1;
|
||||
{% else %}
|
||||
opacity:.4;
|
||||
{% endif %}
|
||||
">
|
||||
<div style="display:flex; align-items:center; gap:6px; margin-bottom:2px;">
|
||||
<span class="stepper-nr" style="
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
width:20px;
|
||||
height:20px;
|
||||
border-radius:50%;
|
||||
font-size:11px;
|
||||
font-weight:700;
|
||||
flex-shrink:0;
|
||||
{% if cls == 'activ' %}
|
||||
background:var(--accent);
|
||||
color:#fff;
|
||||
{% elif cls == 'facut' %}
|
||||
background:var(--ok);
|
||||
color:#fff;
|
||||
{% else %}
|
||||
background:var(--line);
|
||||
color:var(--muted);
|
||||
{% endif %}
|
||||
">
|
||||
{% if cls == 'facut' %}✓{% else %}{{ nr }}{% endif %}
|
||||
</span>
|
||||
<span style="
|
||||
font-size:13px;
|
||||
font-weight:{% if cls == 'activ' %}600{% else %}400{% endif %};
|
||||
color:{% if cls == 'activ' %}var(--ink){% elif cls == 'facut' %}var(--ink){% else %}var(--muted){% endif %};
|
||||
">{{ titlu }}</span>
|
||||
</div>
|
||||
{% if cls == 'activ' %}
|
||||
<p class="muted" style="margin:0; font-size:12px; padding-left:26px;">{{ ajutor }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
@@ -18,5 +18,9 @@
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty">Coada e goala. Trimite o prezentare prin <code>POST /v1/prezentari</code>.</div>
|
||||
<div class="empty">
|
||||
Nicio trimitere inca —
|
||||
<a href="/?tab=import">incepe cu Import</a>
|
||||
sau trimite o prezentare prin API.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="import-section">
|
||||
{% set pas = 1 %}{% include '_stepper.html' %}
|
||||
<div class="card">
|
||||
<h2 style="font-size:15px; margin:0 0 12px;">Import fisier (xlsx / csv)</h2>
|
||||
|
||||
|
||||
@@ -64,6 +64,20 @@
|
||||
button { background:var(--accent); border-color:var(--accent); color:#fff; cursor:pointer; }
|
||||
button:hover { filter:brightness(1.08); }
|
||||
.chk { font-size:13px; color:var(--muted); display:flex; align-items:center; gap:6px; }
|
||||
/* Tab-bar (US-003) */
|
||||
.tab-bar { display:flex; gap:2px; overflow-x:auto; -webkit-overflow-scrolling:touch;
|
||||
border-bottom:1px solid var(--line); margin-bottom:16px; padding-bottom:0;
|
||||
scrollbar-width:none; }
|
||||
.tab-bar::-webkit-scrollbar { display:none; }
|
||||
.tab-link { display:inline-flex; align-items:center; padding:8px 16px; font-size:14px;
|
||||
font-weight:500; color:var(--muted); text-decoration:none; border-radius:6px 6px 0 0;
|
||||
border:1px solid transparent; border-bottom:none; white-space:nowrap;
|
||||
transition:color .12s, background .12s; margin-bottom:-1px; }
|
||||
.tab-link:hover { color:var(--ink); background:var(--line); }
|
||||
.tab-link.tab-activ { color:var(--ink); background:var(--card);
|
||||
border-color:var(--line); border-bottom-color:var(--card); }
|
||||
.tab-panel { min-height:120px; }
|
||||
.status-bar { margin-bottom:12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -10,61 +10,83 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Sectiunea de import fisier: stare initiala = drop zone; HTMX swapeaza in flow -->
|
||||
{% include '_upload.html' %}
|
||||
|
||||
<div class="card banner {% if not blocked %}hidden{% endif %}"
|
||||
hx-get="/_fragments/banner" hx-trigger="every 15s" hx-swap="outerHTML">
|
||||
<strong>Atentie:</strong> {{ blocked }} submission-uri blocate (error / needs_data / needs_mapping).
|
||||
Plasa de siguranta pe pene RAR > 30h. Verifica coada mai jos.
|
||||
<!-- Bara de status (US-002): mereu vizibila, deasupra tab-bar-ului -->
|
||||
<div id="status-bar" class="status-bar card"
|
||||
hx-get="/_fragments/status"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="outerHTML">
|
||||
<div class="empty muted" style="padding:8px 0;">se incarca starea…</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div style="display:flex; gap:24px; flex-wrap:wrap;">
|
||||
<div><div class="muted">Worker</div><div class="{{ 's-sent' if worker_alive else 's-error' }}">
|
||||
{{ 'viu' if worker_alive else 'mort' }}</div></div>
|
||||
<div><div class="muted">RAR</div><div class="{{ 's-sent' if rar_state == 'ok' else 's-error' if 'indisponibil' in rar_state else 'muted' }}">{{ rar_state }}</div></div>
|
||||
<div><div class="muted">Ultimul login RAR</div><div>{{ last_login or '—' }}</div></div>
|
||||
<div><div class="muted">In coada</div><div>{{ counts.get('queued', 0) }}</div></div>
|
||||
<div><div class="muted">Trimise</div><div class="s-sent">{{ counts.get('sent', 0) }}</div></div>
|
||||
<div><div class="muted">Blocate</div><div class="{{ 's-error' if blocked else '' }}">{{ blocked }}</div></div>
|
||||
</div>
|
||||
{% if rar_state != 'ok' %}
|
||||
<p class="muted" style="margin:12px 0 0; font-size:12px;">
|
||||
RAR posibil indisponibil — coada de mai jos arata ultima stare cunoscuta (local), nu live din RAR.
|
||||
</p>
|
||||
{% endif %}
|
||||
<!-- Tab-bar: navigare intre sectiuni -->
|
||||
<div role="tablist" class="tab-bar" aria-label="Sectiuni dashboard">
|
||||
{% set tabs = [
|
||||
("acasa", "Acasa", "tab-acasa"),
|
||||
("import", "Import", "tab-import"),
|
||||
("coada", "Coada", "tab-coada"),
|
||||
("mapari", "Mapari", "tab-mapari"),
|
||||
("cont", "Cont", "tab-cont"),
|
||||
("nomenclator", "Nomenclator", "tab-nomenclator")
|
||||
] %}
|
||||
{% for tab_id, tab_label, tab_elem_id in tabs %}
|
||||
<a id="{{ tab_elem_id }}"
|
||||
role="tab"
|
||||
href="/?tab={{ tab_id }}"
|
||||
aria-selected="{{ 'true' if active_tab == tab_id else 'false' }}"
|
||||
aria-controls="tab-panel"
|
||||
class="tab-link{% if active_tab == tab_id %} tab-activ{% endif %}"
|
||||
tabindex="{{ '0' if active_tab == tab_id else '-1' }}"
|
||||
hx-get="/_fragments/{{ tab_id }}"
|
||||
hx-target="#tab-panel"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="/?tab={{ tab_id }}">{{ tab_label }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- incarcat o data; NU poll (sa nu stergem o selectie in curs). Se re-randeaza la salvare. -->
|
||||
<div hx-get="/_fragments/mapari" hx-trigger="load" hx-swap="outerHTML">
|
||||
<div class="card"><div class="empty">se incarca mapari…</div></div>
|
||||
<!-- Panou activ: randat server-side la full load; HTMX inlocuieste continutul la click pe tab -->
|
||||
<div id="tab-panel" role="tabpanel" aria-labelledby="tab-{{ active_tab }}" class="tab-panel">
|
||||
{{ panel_html | safe }}
|
||||
</div>
|
||||
|
||||
<div hx-get="/_fragments/cont" hx-trigger="load" hx-swap="outerHTML">
|
||||
<div class="card"><div class="empty">se incarca cont…</div></div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
/* Navigare cu sageti intre tab-uri (ARIA pattern) */
|
||||
var tablist = document.querySelector('[role="tablist"]');
|
||||
if (!tablist) return;
|
||||
var tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
|
||||
tablist.addEventListener('keydown', function(e) {
|
||||
var idx = tabs.indexOf(document.activeElement);
|
||||
if (idx === -1) return;
|
||||
var next = -1;
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
||||
next = (idx + 1) % tabs.length;
|
||||
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
||||
next = (idx - 1 + tabs.length) % tabs.length;
|
||||
} else if (e.key === 'Home') {
|
||||
next = 0;
|
||||
} else if (e.key === 'End') {
|
||||
next = tabs.length - 1;
|
||||
}
|
||||
if (next !== -1) {
|
||||
e.preventDefault();
|
||||
tabs[next].focus();
|
||||
}
|
||||
});
|
||||
|
||||
<div class="card">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 12px;">
|
||||
<h2 style="font-size:15px; margin:0;">Coada submissions</h2>
|
||||
<span style="margin-left:auto; display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<a class="cardlink" href="/v1/audit/export?status=sent" download>export CSV: trimise</a>
|
||||
<a class="cardlink" href="/v1/audit/export?status=all" download>toate</a>
|
||||
</span>
|
||||
</div>
|
||||
<div hx-get="/_fragments/submissions" hx-trigger="load, every 10s" hx-swap="innerHTML">
|
||||
<div class="empty">se incarca…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<details>
|
||||
<summary style="cursor:pointer; font-size:15px; font-weight:600;">Nomenclator RAR (coduri prestatii)</summary>
|
||||
<div style="margin-top:12px;" hx-get="/_fragments/nomenclator" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="empty">se incarca…</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
/* La click pe tab: actualizeaza aria-selected + tabindex */
|
||||
tabs.forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
tabs.forEach(function(t) {
|
||||
t.setAttribute('aria-selected', 'false');
|
||||
t.setAttribute('tabindex', '-1');
|
||||
t.classList.remove('tab-activ');
|
||||
});
|
||||
tab.setAttribute('aria-selected', 'true');
|
||||
tab.setAttribute('tabindex', '0');
|
||||
tab.classList.add('tab-activ');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user