- POST /v1/prezentari: camp optional 'raspuns' (minim implicit / complet); forma minima = 6 chei fixe (index, submission_id, vehicul, status, motiv, id_prezentare), formatul detaliat ramane la cerere - corelare rezultat<->cerere: index 0-based + ecou vin/nr_inmatriculare - sugestia principala de cod RAR pe nemapate (GOLD partajat > SILVER > embeddings > fuzzy peste prag), in motiv si in nemapate[].sugestie (si pe dry-run /valideaza); suggestion-only, nu se aplica automat - lista Trimiteri: coloana # cu id-ul trimiterii (desktop + mobil) - README: sectiune 'Mapari si sugestii de cod RAR'; contract actualizat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
342 lines
15 KiB
HTML
342 lines
15 KiB
HTML
{#
|
||
OOB: actualizeaza inputul id="f-page" din #filtre-trimiteri.
|
||
Reincarcarea (hx-include="#filtre-trimiteri") preia automat pagina curenta.
|
||
Elementul OOB e extras din continutul normal de HTMX inainte de swap in #submissions-wrap.
|
||
#}
|
||
<input type="hidden" id="f-page" name="page" value="{{ page | default(1) }}" hx-swap-oob="true">
|
||
|
||
{# OOB: sincronizeaza filtrul "doar tinute" (#f-held) cu starea randata acum, ca reincarcarile
|
||
(trimiteriChanged / paginare) sa-l pastreze in loc sa revina la lista completa. #}
|
||
<input type="hidden" id="f-held" name="held" value="{{ f_held | default('') }}" hx-swap-oob="true">
|
||
|
||
{# OOB: banner bulk "Trimite toate (N)" re-randat la fiecare reincarcare a listei (fix staleness). #}
|
||
{% with banner_oob = true %}{% include '_bulk_held_banner.html' %}{% endwith %}
|
||
|
||
{# OOB: re-randeaza pill-urile de stare (in bara de filtre, in afara #submissions-wrap) cu
|
||
contoarele si starea activa proaspete la fiecare reincarcare a tabelului. #}
|
||
<span hx-swap-oob="innerHTML:#pills-categorii">{% include '_pills.html' %}</span>
|
||
|
||
{# Versiunea datelor cu care s-a randat tabelul; pollerul "Date noi" o compara. #}
|
||
<span id="trimiteri-versiune" data-v="{{ versiune_trimiteri | default('') }}" hidden></span>
|
||
|
||
{% if bulk_message %}
|
||
{# Sumar actiune bulk: afisat dupa bulk-fix, disparut la urmatoarea reincarcare. #}
|
||
<div class="bulk-message" role="status" aria-live="polite"
|
||
style="font-size:13px; color:var(--ink); background:var(--card2);
|
||
border:1px solid var(--line); border-radius:6px;
|
||
padding:6px 10px; margin-bottom:8px;">
|
||
{{ bulk_message }}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Chip filtru activ "doar tinute" cu buton de golire — filtrul held e sticky
|
||
prin OOB, deci ii dam un off-switch explicit. #}
|
||
{% if f_held %}
|
||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px; font-size:var(--fs-sm);">
|
||
<span style="display:inline-flex; align-items:center; gap:6px; padding:2px 10px; border-radius:99px;
|
||
border:1px solid var(--warn); color:var(--warn); font-weight:600;">
|
||
Doar tinute manual
|
||
</span>
|
||
<button type="button"
|
||
onclick="var h=document.getElementById('f-held'); if(h){h.value='';} var f=document.getElementById('filtre-trimiteri'); if(f&&window.htmx){htmx.trigger(f,'submit');}"
|
||
style="background:none; border:none; color:var(--muted); cursor:pointer;
|
||
text-decoration:underline; font-size:var(--fs-sm);">
|
||
Arata tot
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if rows %}
|
||
{# Form bulk cu DOUA actiuni: (1) aplica cod RAR la selectate (bulk-fix),
|
||
(2) sterge selectate (sterge-bulk). Selectia opereaza DOAR pe randuri blocate
|
||
(gestionabil); sent/sending/queued nu au checkbox (read-only).
|
||
Butonul "Aplica cod" foloseste hx-post propriu (override form action).
|
||
hx-disinherit="hx-confirm" pe form => butonul aplica-cod NU mosteneste confirmare. #}
|
||
<form id="bulk-trimiteri"
|
||
hx-post="/trimiteri/sterge-bulk"
|
||
hx-target="#submissions-wrap"
|
||
hx-swap="innerHTML"
|
||
hx-confirm="Stergi definitiv trimiterile selectate?"
|
||
hx-disinherit="hx-confirm"
|
||
style="margin:0;">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
|
||
{# Bara contextuala bulk (5.22): randata inactiva (ascunsa prin CSS), afisata de
|
||
JS (base.html) cand >= 1 checkbox e bifat. Contoarele .bulk-n / .bulk-n-txt
|
||
sunt actualizate client-side. Endpoint-urile si CSRF raman neschimbate;
|
||
"Sterge" pastreaza hx-confirm-ul form-ului, "Aplica" e exclus (hx-disinherit). #}
|
||
<div id="bulk-bar" class="bulk-bar" role="toolbar" aria-label="Actiuni pe randurile selectate">
|
||
<span style="font-weight:700;"><span class="bulk-n">0</span> selectate</span>
|
||
<span style="color:var(--muted);" aria-hidden="true">·</span>
|
||
<input type="text" name="cod_prestatie" id="bulk-fix-cod"
|
||
placeholder="Cod RAR (ex: OE-1)"
|
||
autocomplete="off" autocapitalize="characters"
|
||
style="width:120px; font-size:12px; padding:3px 7px;
|
||
border:1px solid var(--line); border-radius:5px;
|
||
background:var(--card2); color:var(--ink); font-family:var(--font-mono);"
|
||
aria-label="Cod RAR de aplicat la randurile selectate">
|
||
<button type="button"
|
||
hx-post="/trimiteri/bulk-fix"
|
||
hx-target="#submissions-wrap"
|
||
hx-swap="innerHTML"
|
||
style="background:var(--card); color:var(--accent); border:1px solid var(--accent);
|
||
font-size:12.5px; padding:4px 10px; border-radius:5px; cursor:pointer;"
|
||
aria-label="Aplica codul RAR la randurile blocate selectate">
|
||
Aplica acest cod la <span class="bulk-n">0</span> <span class="bulk-n-txt">randuri</span>
|
||
</button>
|
||
<span style="color:var(--muted); font-size:11px; padding:0 2px;" aria-hidden="true">|</span>
|
||
<button type="submit" id="bulk-sterge-btn"
|
||
style="background:var(--card); color:var(--err); border:1px solid var(--err);
|
||
font-size:12.5px; padding:4px 10px; border-radius:5px; cursor:pointer;">
|
||
Sterge <span class="bulk-n">0</span> <span class="bulk-n-txt">randuri</span>
|
||
</button>
|
||
<button type="button" id="bulk-deselect"
|
||
style="margin-left:auto; background:none; border:none; color:var(--muted);
|
||
cursor:pointer; text-decoration:underline; font-size:12px;">
|
||
Deselecteaza
|
||
</button>
|
||
</div>
|
||
<div class="bulk-hint">
|
||
Aplica codul RAR doar operatiilor fara cod din randurile selectate, apoi le repune in coada.
|
||
</div>
|
||
|
||
{# Tabel compact trimiteri (grid CSS, PRD 5.22).
|
||
Desktop: un rand per trimitere sub un header de coloane; status = bulina cu
|
||
tooltip, mediu = litera T/P, multi-cod = primul cod + badge +N.
|
||
Mobil (max-width:767px): header ascuns, randul revine la 2 linii
|
||
(placuta / cod·operatie·data) cu litera + bulina in dreapta. #}
|
||
<ul class="lista-trimiteri-slim" role="list"
|
||
aria-label="Lista trimiteri">
|
||
|
||
{# Header de coloane — doar desktop (CSS); numele starii/mediului stau aici,
|
||
celulele raman semnale compacte (bulina, litera). #}
|
||
<li class="trimiteri-head" aria-hidden="true">
|
||
<span></span>
|
||
<span>#</span>
|
||
<span>Vehicul</span>
|
||
<span>Cod RAR</span>
|
||
<span>Operatie</span>
|
||
<span>Data</span>
|
||
<span class="th-c">Status</span>
|
||
<span class="th-c">Mediu</span>
|
||
<span></span>
|
||
</li>
|
||
|
||
{% for r in rows %}
|
||
{# Rand clickabil (role=button, Enter/Space in JS din base.html) — deschide
|
||
modalul global de detaliu (#detaliu-modal-body). #}
|
||
<li id="trimitere-row-{{ r.id }}"
|
||
class="trimitere-slim"
|
||
data-detaliu-id="{{ r.id }}"
|
||
hx-get="/_fragments/trimitere/{{ r.id }}"
|
||
hx-target="#detaliu-modal-body"
|
||
hx-swap="innerHTML"
|
||
role="button" tabindex="0"
|
||
aria-haspopup="dialog"
|
||
style="cursor:pointer;"
|
||
title="Click pentru detaliul complet">
|
||
|
||
{# Checkbox — nu declanseaza modalul (stopPropagation). Vizibil DOAR pe
|
||
randurile gestionabile; latimea fixa previne reflow. #}
|
||
<div class="c-check" onclick="event.stopPropagation();">
|
||
{% if r.gestionabil %}
|
||
<input type="checkbox" name="submission_id" value="{{ r.id }}"
|
||
aria-label="Selecteaza trimiterea #{{ r.id }} pentru stergere">
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Numarul trimiterii (id din baza) — aceeasi valoare ca `submission_id`
|
||
din raspunsul API si ca titlul modalului de detaliu/editare. #}
|
||
<span class="c-id">#{{ r.id }}</span>
|
||
|
||
{# Vehicul: placuta (identificator primar); fallback VIN scurt / 'fara numar'
|
||
(niciodata em-dash izolat). #}
|
||
{% if r.prez.vehicul_nr and r.prez.vehicul_nr != '—' %}
|
||
<div class="slim-vin">{{ r.prez.vehicul_nr }}</div>
|
||
{% elif r.prez.vin_scurt and r.prez.vin_scurt != '—' %}
|
||
<div class="slim-vin muted">{{ r.prez.vin_scurt }}</div>
|
||
{% else %}
|
||
<div class="slim-vin muted">fara numar</div>
|
||
{% endif %}
|
||
|
||
{# Pe desktop .c-meta e display:contents (copiii devin celule de grid);
|
||
pe mobil e linia 2 (flex) cu separatoare "·" din CSS. #}
|
||
<div class="c-meta">
|
||
{% set _cods = r.prez.coduri_rar or [] %}
|
||
<span class="c-cod">
|
||
{% if _cods %}
|
||
<span class="cod-rar-cod">{{ _cods[0] }}</span>
|
||
{%- if _cods | length > 1 -%}
|
||
<span class="cod-plus" title="{{ _cods | join(' · ') }}">+{{ _cods | length - 1 }}</span>
|
||
{%- endif %}
|
||
{% else %}
|
||
<span class="cod-rar-cod cod-rar-sub muted">nemapat</span>
|
||
{% endif %}
|
||
</span>
|
||
<span class="c-op">{{ r.prez.operatie }}</span>
|
||
<span class="c-data">{% if r.prez.data_prestatie and r.prez.data_prestatie != '—' %}{{ r.prez.data_prestatie }}{% endif %}</span>
|
||
</div>
|
||
|
||
{# Status: doar bulina colorata; numele complet in title + aria-label. #}
|
||
<span class="c-status">
|
||
<span class="dot-stare {{ r.stare_css }}" title="{{ r.stare_text }}"
|
||
role="img" aria-label="{{ r.stare_text }}"></span>
|
||
</span>
|
||
|
||
{# Mediu: litera compacta — P fill atentie (productie, risc L.142),
|
||
T contur discret (testare). Numele complet in title. #}
|
||
<span class="c-env">
|
||
{% if r.rar_env %}
|
||
{% set _eb = eticheta_env(r.rar_env) %}
|
||
<span class="env-l {{ 'env-l-prod' if 'prod' in _eb[1] else 'env-l-test' }}"
|
||
title="{{ _eb[0] }}" role="img" aria-label="Mediu RAR: {{ _eb[0] }}">{{ _eb[0][0] }}</span>
|
||
{% endif %}
|
||
</span>
|
||
|
||
{# Actiuni: buton "Trimite" DOAR pe randurile tinute (queued AND held=1). #}
|
||
<div class="c-act">
|
||
{% if r.tinut %}
|
||
<form hx-post="/trimitere/{{ r.id }}/trimite-acum"
|
||
hx-target="#submissions-wrap" hx-swap="innerHTML"
|
||
hx-confirm="Trimit aceasta prezentare la RAR? Declararea este ireversibila (FINALIZATA)."
|
||
onclick="event.stopPropagation();"
|
||
onkeydown="event.stopPropagation();"
|
||
style="margin:0;">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
<button type="submit" class="btn-trimite-rand"
|
||
onclick="event.stopPropagation();"
|
||
style="padding:2px 10px; font-size:var(--fs-xs);
|
||
border:1px solid var(--accent); border-radius:99px; cursor:pointer;
|
||
background:var(--accent); color:#fff; font-weight:600;">
|
||
Trimite
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Sub-linie de eroare: textul BRUT (mesaj RAR/validare), compact, un rand,
|
||
doar pe error/needs_data (eroare_bruta e "" in rest). #}
|
||
{% if r.eroare_bruta %}
|
||
<div class="c-err" title="{{ r.eroare_bruta }}">{{ r.eroare_bruta }}</div>
|
||
{% endif %}
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</form>
|
||
|
||
{#
|
||
Paginare numerotata.
|
||
Afisata doar cand exista mai mult de o pagina.
|
||
Fiecare link pastreaza filtrele curente (status, vehicul, data_de, data_pana).
|
||
Pagina curenta: aria-current="page" (semantic).
|
||
#}
|
||
{% if total is defined %}
|
||
<div aria-live="polite"
|
||
style="font-size:12px; color:var(--muted); text-align:right; margin-top:6px; margin-bottom:2px;">
|
||
{% if total == 0 %}
|
||
0 trimiteri
|
||
{% else %}
|
||
{{ page_start }}–{{ page_end }} din {{ total }}
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if pages is defined and pages > 1 %}
|
||
{#
|
||
Construim param-string pentru filtrele curente (fara page) — refolosit in fiecare link.
|
||
Filtrul status vine din pill-uri (nu din form); il pastram in URL.
|
||
#}
|
||
{% set pq = "" %}
|
||
{% if f_status %}{% set pq = pq + "&status=" + f_status %}{% endif %}
|
||
{% if f_vehicul %}{% set pq = pq + "&vehicul=" + f_vehicul %}{% endif %}
|
||
{% if f_data_de %}{% set pq = pq + "&data_de=" + f_data_de %}{% endif %}
|
||
{% if f_data_pana %}{% set pq = pq + "&data_pana=" + f_data_pana %}{% endif %}
|
||
{% if f_held %}{% set pq = pq + "&held=" + f_held %}{% endif %}
|
||
|
||
<nav aria-label="Paginare trimiteri"
|
||
style="display:flex; justify-content:center; gap:4px; flex-wrap:wrap; margin-top:10px;">
|
||
|
||
{# Buton Anterior #}
|
||
{% if page > 1 %}
|
||
<button type="button"
|
||
hx-get="/_fragments/submissions?page={{ page - 1 }}{{ pq }}"
|
||
hx-target="#submissions-wrap"
|
||
hx-swap="innerHTML"
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px; cursor:pointer;
|
||
border:1px solid var(--line); background:var(--card); color:var(--fg);"
|
||
aria-label="Pagina anterioara">
|
||
«
|
||
</button>
|
||
{% else %}
|
||
<button type="button" disabled
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px;
|
||
border:1px solid var(--line); background:var(--card); color:var(--muted);
|
||
opacity:0.4; cursor:default;"
|
||
aria-label="Pagina anterioara (indisponibila)">
|
||
«
|
||
</button>
|
||
{% endif %}
|
||
|
||
{# Numerele de pagina #}
|
||
{% for p in range(1, pages + 1) %}
|
||
{% if p == page %}
|
||
<button type="button"
|
||
aria-current="page"
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px; cursor:default;
|
||
border:1px solid var(--accent); background:var(--accent); color:#fff;
|
||
font-weight:700;">
|
||
{{ p }}
|
||
</button>
|
||
{% else %}
|
||
<button type="button"
|
||
hx-get="/_fragments/submissions?page={{ p }}{{ pq }}"
|
||
hx-target="#submissions-wrap"
|
||
hx-swap="innerHTML"
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px; cursor:pointer;
|
||
border:1px solid var(--line); background:var(--card); color:var(--fg);">
|
||
{{ p }}
|
||
</button>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{# Buton Urmator #}
|
||
{% if page < pages %}
|
||
<button type="button"
|
||
hx-get="/_fragments/submissions?page={{ page + 1 }}{{ pq }}"
|
||
hx-target="#submissions-wrap"
|
||
hx-swap="innerHTML"
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px; cursor:pointer;
|
||
border:1px solid var(--line); background:var(--card); color:var(--fg);"
|
||
aria-label="Pagina urmatoare">
|
||
»
|
||
</button>
|
||
{% else %}
|
||
<button type="button" disabled
|
||
style="padding:3px 10px; border-radius:6px; font-size:13px;
|
||
border:1px solid var(--line); background:var(--card); color:var(--muted);
|
||
opacity:0.4; cursor:default;"
|
||
aria-label="Pagina urmatoare (indisponibila)">
|
||
»
|
||
</button>
|
||
{% endif %}
|
||
|
||
</nav>
|
||
{% endif %}
|
||
|
||
{% elif filtru_activ %}
|
||
<div class="empty">
|
||
Nimic pe filtrul curent.
|
||
<a href="#"
|
||
onclick="var f=document.getElementById('filtre-trimiteri'); if(f) f.reset(); return true;"
|
||
hx-get="/_fragments/submissions" hx-target="#submissions-wrap" hx-swap="innerHTML">
|
||
sterge filtrele
|
||
</a>
|
||
</div>
|
||
{% else %}
|
||
<div class="empty">
|
||
Nicio trimitere inca —
|
||
<a href="/?tab=acasa">incepe cu un import</a>
|
||
sau trimite o prezentare prin API.
|
||
</div>
|
||
{% endif %}
|