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:
@@ -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">☰</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);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user