feat(ux): import compact + preview format Trimiteri + navigatie + scoatere auto_send (5.11)

8 stories TDD (echipa Sonnet, lead orchestreaza). US-001 scoate hold-ul auto_send din mapare
(has_no_auto_send->False, simbol pastrat; cod rezolvat->queued). US-002 scoate bifa auto_send
din UI. US-003 preview pas 3 in format .tabel-trimiteri (STARI_PREVIEW + nota_umana_preview,
fara repr Python; view-model prez). US-004 filtre layout/stil ca referinta + buton Custom.
US-005 navigatie Trimiteri/Mapari sub contoare pe toate paginile. US-006 import <details> nativ
colapsabil. US-007 post-commit reveal (OOB _coada/_status + HX-Trigger). US-008 auto-refresh
dupa actiuni (nudge eliminat).

VERIFY context curat PASS (8/8). /code-review high: 3 buguri reparate (tab nav la self-refresh,
pill Custom valori stale, nota_umana_preview precedenta needs_mapping). 934 passed, 1 skipped.
Backend trimitere + schema NEATINSE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-26 15:16:28 +00:00
parent 412102b9b1
commit 283299ff20
34 changed files with 3079 additions and 389 deletions

View File

@@ -148,21 +148,20 @@
font-size:12px; font-weight:600; cursor:pointer; background:transparent;
border:1.5px solid var(--line); color:var(--muted); min-height:30px;
transition:background .15s, color .15s; }
.pill-cat:hover { filter:brightness(1.1); }
/* Hover: color-mix pe culoarea curenta a pill-ului (categoria sa), nu filter:brightness
(care producea rosu plin ilizibil pe pill-uri colorate). Activ suprima hover-ul. */
.pill-cat:hover { background:color-mix(in srgb, currentColor 12%, transparent); }
.pill-cat:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.pill-cat-n { font-size:11px; font-weight:700; color:var(--card); padding:0 5px;
border-radius:99px; min-width:18px; text-align:center; }
/* Activ categorie: umplere cu culoarea categoriei (currentColor = var injectat inline) */
.pill-cat[aria-pressed="true"] { background:currentColor; color:var(--card); border-color:currentColor; }
.pill-cat[aria-pressed="true"] .pill-cat-n { background:var(--card) !important; color:currentColor; }
/* Activ suprima hover: pastram culoarea activa, nu o mixam din nou */
.pill-cat[aria-pressed="true"]:hover { background:currentColor; }
/* Reset "Toate" activ = --accent plin (nu culoarea categoriei) */
.pill-cat-reset[aria-pressed="true"] { background:var(--accent); color:#fff; border-color:var(--accent); }
/* Nudge "Date noi": apare doar cand pollerul usor detecteaza schimbari; tabelul nu se
schimba singur niciodata, utilizatorul reincarca cand vrea. */
#nudge-trimiteri { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0 0 12px;
padding:8px 12px; border-radius:8px; font-size:13px;
border:1px solid var(--accent);
background:color-mix(in srgb, var(--accent) 12%, var(--card)); }
#nudge-trimiteri[hidden] { display:none; }
#nudge-trimiteri button { font-size:13px; padding:5px 12px; min-height:32px; }
.pill-cat-reset[aria-pressed="true"]:hover { background:var(--accent); }
.s-queued{color:var(--accent);} .s-sending{color:var(--warn);} .s-sent{color:var(--ok);}
.s-error,.s-needs_data,.s-needs_mapping{color:var(--err);}
.s-ok{color:var(--ok);}
@@ -267,6 +266,41 @@
border-radius:6px; cursor:pointer; min-height:36px; white-space:nowrap; }
.kebab-menu button:hover, .kebab-menu a:hover { background:var(--line); }
.kebab-menu button.danger { color:var(--err); }
/* === Accordion import compact (US-006 — regiune CSS disjuncta) ===
<details id="import-details"> invelete stepper + upload pe Acasa.
Atribut `open` setat de server din `are_trimiteri`:
False (first-run) → open; True (returning) → colapsat.
Degradare fara JS: returning-user colapsat, first-run deschis — ambele corecte
fara toggle JS (un toggle JS pur ar lasa returning-user fara-JS cu ecranul deschis).
aria-expanded + focus: native <details> le gestioneaza automat. */
#import-details { margin-bottom:16px; border:1px solid var(--accent); border-radius:8px;
background:var(--card); overflow:hidden; }
#import-details > summary { display:flex; align-items:center; gap:8px; cursor:pointer;
user-select:none; list-style:none; padding:10px 16px;
font-weight:600; font-size:14px; color:var(--ink); }
#import-details > summary::-webkit-details-marker { display:none; }
#import-details > summary::marker { display:none; }
#import-details > summary::before { content:"▶"; font-size:10px; color:var(--accent);
flex-shrink:0; transition:transform .15s; }
#import-details[open] > summary::before { transform:rotate(90deg); }
#import-details[open] > summary { border-bottom:1px solid var(--line); }
#import-details > summary:hover { background:color-mix(in srgb, var(--accent) 8%, var(--card)); }
#import-details > summary:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
/* Continutul (stepper + card upload): padding si bordul cardului interior sunt suprastate
de bordul exterior al #import-details — scoatem border duplicat de pe .card intern. */
#import-details #import-section { padding:0; }
#import-details #import-section > .card { border-left:none; border-right:none;
border-bottom:none; border-radius:0;
margin-bottom:0; }
/* === Sfarsit regiune accordion import === */
/* === Inceput regiune nav links status-bar (US-005) ===
Linkuri Trimiteri + Mapari sub contoare; marcaj activ cu aria-current/status-nav-activ. */
.status-nav-link { color:var(--accent); text-decoration:none; padding:2px 6px; border-radius:4px;
transition:background .1s; }
.status-nav-link:hover { background:color-mix(in srgb, var(--accent) 10%, transparent); }
.status-nav-link.status-nav-activ { color:var(--ink); font-weight:600; }
.status-nav-link.status-nav-activ:hover { background:color-mix(in srgb, var(--ink) 8%, transparent); }
/* === Sfarsit regiune nav links status-bar === */
/* Tabel cu cautare + paginare client-side (data-dt). Maparile pot creste la sute de randuri;
filtram/paginez DOM-ul deja randat, fara cereri suplimentare. Vezi scriptul din base.html. */
input[type=search] { font:inherit; background:var(--bg); color:var(--ink); border:1px solid var(--line);
@@ -311,6 +345,25 @@
@media (max-width:1024px) {
.tabel-trimiteri .col-actualizat { display:none; }
}
/* === Preview import: coloane extra fata de tabelul Trimiteri.
SCOPAT prin .tabel-trimiteri (clasa partajata). Regiune separata —
nu atinge coloanele existente (col-chk/id/stare/data/rar/actualizat).
Suma latimi fixe: col-id(48) + col-stare(104) + col-data(104) +
col-km(76) + col-note(176) + col-verificat(80) + col-actiuni(92) = 680px.
Restul (~600px la 1280px) revine lui col-vehicul + col-operatie (fluid). === */
.tabel-trimiteri .col-km { width:76px; }
.tabel-trimiteri .col-note { width:176px; }
.tabel-trimiteri .col-verificat{ width:80px; }
.tabel-trimiteri .col-actiuni { width:92px; }
/* Randul de editare inline iese din grila table-layout:fixed (display:block),
astfel formularul nu e constrans de latimile coloanelor individuale.
Salveaza/Anuleaza sunt mereu vizibile (overflow:visible, nu clip). */
.tabel-trimiteri tr.preview-edit { display:block; }
.tabel-trimiteri tr.preview-edit > td { display:block; width:100%; box-sizing:border-box; padding:0; border:none; }
/* Pe mobil (<768px): pseudo-eticheta goala (data-eticheta="") nu lasa spatiu gol. */
@media (max-width:767px) {
.tabel-trimiteri td[data-eticheta=""]::before { display:none; }
}
/* === Modal detaliu: fereastra modala globala, in afara zonei de poll
(#submissions-wrap). Backdrop + dialog centrat pe desktop; focus-trap +
scroll-lock + inert pe <main> sunt in JS. Varianta full-screen mobil: vezi blocul
@@ -435,12 +488,16 @@
<header>
{# Celula stanga: logo ROMFAST #}
<div class="header-left">
{# Logo PNG real, RGBA transparent — ok pe toate temele fara filtre. #}
<img src="/static/romfast_logo.png" alt="ROMFAST" class="brand-logo">
{# Logo PNG real, RGBA transparent — ok pe toate temele fara filtre.
Invelit in <a href="/"> pentru a naviga la Trimiteri (Acasa) de pe orice pagina. #}
<a href="/" style="display:inline-flex; align-items:center; text-decoration:none;">
<img src="/static/romfast_logo.png" alt="ROMFAST" class="brand-logo">
</a>
</div>
{# Celula centru: titlu + badge env mic #}
{# Celula centru: titlu + badge env mic.
Titlul linkeaza la / (Trimiteri) ca si logo-ul. #}
<div class="header-center">
<h1>Gateway RAR AUTOPASS</h1>
<a href="/" style="text-decoration:none; color:inherit;"><h1>Gateway RAR AUTOPASS</h1></a>
<span class="env">{{ rar_env }}</span>
</div>
{# Celula dreapta: comutator tema + versiune + meniu cont #}
@@ -457,6 +514,8 @@
aria-haspopup="true" aria-expanded="false" aria-controls="cont-menu"
aria-label="Meniu cont" title="Meniu cont">&#9776;</button>
<div id="cont-menu" class="cont-menu" role="menu" aria-labelledby="cont-menu-toggle" hidden>
{# Prima intrare: Trimiteri (Acasa) — pagina principala cu import + lista trimiterilor. #}
<a role="menuitem" href="/">Trimiteri</a>
{# Mapari, cu badge needs_mapping. #}
{% set _mapari_badge = (badges.mapari if (badges is defined and badges and badges.mapari) else 0) %}
<a role="menuitem" href="/?tab=mapari">Mapari{% if _mapari_badge %}<span class="tab-badge" aria-hidden="true" style="display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; margin-left:6px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;">{{ _mapari_badge }}</span>{% endif %}</a>
@@ -800,11 +859,51 @@
})();
</script>
<script>
// Filtrare stare prin pill-uri + reincarcare manuala a tabelului. Tabelul NU se mai
// schimba singur (fara poll periodic pe #submissions-wrap): un poller usor verifica
// doar versiunea datelor si arata nudge-ul "Date noi" cand difera. Reincarcarea
// (pill, nudge sau actiune) trece prin form -> pastreaza filtrul/pagina curenta.
// Filtrare stare prin pill-uri + reincarcare a tabelului (manuala sau auto din poller).
// Reincarcarea trece prin form -> pastreaza filtrul/pagina curenta (hx-include).
(function() {
// Quick-pills de data (Azi/7 zile/30 zile/Custom): seteaza interval sau dezvaluie campuri manuale.
// NU modifica f-status — pastreaza pill-ul de stare activ curent.
window.setDataRange = function(btn, range) {
var form = document.getElementById('filtre-trimiteri');
if (!form) return;
var de = document.getElementById('f-data-de');
var pana = document.getElementById('f-data-pana');
var hp = document.getElementById('f-page'); if (hp) hp.value = '1';
var customPanel = document.getElementById('custom-date-fields');
// Marcheaza pill-ul de data activ, reseteaza celelalte quick-pills
document.querySelectorAll('.pill-data').forEach(function(b) {
b.setAttribute('aria-pressed', 'false');
});
if (btn) btn.setAttribute('aria-pressed', 'true');
// Custom: dezvaluie campurile manuale, asteapta inputul utilizatorului.
// NU face submit automat; form-ul submite la change (hx-trigger="change").
if (range === 'custom') {
// Goleste valorile ramase de la ultimul preset — utilizatorul porneste de la curat.
if (de) de.value = '';
if (pana) pana.value = '';
if (customPanel) customPanel.style.display = 'flex';
if (de) de.focus();
return;
}
// Preset-uri: ascunde campurile manuale, seteaza intervalul si trimite imediat.
if (customPanel) customPanel.style.display = 'none';
var azi = new Date();
// Formateaza data ca YYYY-MM-DD in zona locala (nu UTC, ca sa nu cada cu -1 zi noaptea)
function fmt(d) {
return d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0');
}
var from, to;
if (range === 'azi') { from = fmt(azi); to = fmt(azi); }
else if (range === '7zile') { var d7 = new Date(azi); d7.setDate(d7.getDate() - 6); from = fmt(d7); to = fmt(azi); }
else if (range === '30zile') { var d30 = new Date(azi); d30.setDate(d30.getDate() - 29); from = fmt(d30); to = fmt(azi); }
else { from = ''; to = ''; }
if (de) de.value = from;
if (pana) pana.value = to;
if (form.requestSubmit) form.requestSubmit(); else form.submit();
};
// Pill de stare: scrie campul hidden, reseteaza pagina la 1 si re-trimite filtrul.
window.filtreazaStare = function(btn, status) {
var form = document.getElementById('filtre-trimiteri');
@@ -817,30 +916,33 @@
if (btn) btn.setAttribute('aria-pressed', 'true');
if (form.requestSubmit) form.requestSubmit(); else form.submit();
};
// Reincarca tabelul pastrand filtrul curent (hx-include #filtre-trimiteri) si ascunde nudge-ul.
// Reincarca tabelul pastrand filtrul curent (hx-include #filtre-trimiteri).
window.reincarcaTrimiteri = function() {
var n = document.getElementById('nudge-trimiteri'); if (n) n.hidden = true;
if (window.htmx) htmx.trigger('#submissions-wrap', 'reincarcaTrimiteri');
};
// Poller "Date noi": compara versiunea datelor cu cea cu care s-a randat tabelul.
// Daca difera, arata nudge-ul; daca nu, nu atinge nimic. JSON usor, fara re-render.
// Poller auto-refresh: compara versiunea datelor cu cea cu care s-a randat tabelul.
// Daca difera (schimbari externe — ex. worker a procesat trimiteri), reincarca automat
// pastrand filtrul curent. Fara nudge "Date noi" — auto-refresh e mai consistent.
// Decizie: nudge eliminat; distinctia propriu vs extern e imposibila pe client
// fara sesiune dedicata — auto-refresh acoper ambele cazuri (US-008).
var INTERVAL = 20000;
function versiuneCurenta() {
var e = document.getElementById('trimiteri-versiune');
return e ? e.getAttribute('data-v') : null;
}
var _verifica_in_curs = false;
function verifica() {
if (versiuneCurenta() === null) return; // tabelul nu e pe ecran (alt tab)
var nudge = document.getElementById('nudge-trimiteri');
if (!nudge || !nudge.hidden) return; // deja afisat -> nu re-cere
if (versiuneCurenta() === null) return; // tabelul nu e pe ecran (alt tab)
if (_verifica_in_curs) return; // evita suprapuneri
_verifica_in_curs = true;
fetch('/_fragments/trimiteri-versiune', { headers: { 'X-Requested-With': 'fetch' } })
.then(function(r) { return r.ok ? r.json() : null; })
.then(function(d) {
if (!d) return;
if (d.v !== versiuneCurenta()) nudge.hidden = false;
if (d && d.v !== versiuneCurenta()) reincarcaTrimiteri();
})
.catch(function() {});
.catch(function() {})
.finally(function() { _verifica_in_curs = false; });
}
setInterval(verifica, INTERVAL);
})();