Files
rar-autopass/app/web/templates/_submissions.html
Claude Agent 3b5cf7a7d9 feat(5.19): auto-send toggle per cont + tinere manuala randuri (held)
Comutator accounts.auto_send_enabled per cont: Auto OFF (default) tine randurile
la ingestie (submissions.held=1), worker-ul (claim_one AND held=0) le sare pana la
eliberare umana (per rand/bulk/auto-release OFF->ON). Snapshot held prin chokepoint
unic held_for_account pe toate caile de ingestie (API, import, reresolve, reactivare).

- schema/migrare: coloana held + index partial idx_submissions_held; auto_send_enabled
- API: echo onest held+motiv (US-010), ruta /prezentari/{id}/trimite-acum
- web: toggle header, modal confirmare tipata, buton Trimite per rand + Trimite toate,
  banner coada tinuta imbatranita (L.142), contor "In asteptare (manual)"
- worker: expire_held (US-008, inchide gaura retentie PII), metrics held gauges
- ops: tools/carantina_held + runbook rollback (R4)

Nota review (/code-review high): re-snapshot held lipseste pe caile repune/corectie
(requeue_submission, post_corectie, bulk-fix) — de aliniat separat cu create_prezentari.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 08:34:40 +00:00

286 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{#
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: 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 (US-010 PRD 5.15): 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 %}
{% if rows %}
{# Form bulk cu DOUA actiuni: (1) aplica cod RAR la selectate (bulk-fix, US-010),
(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 }}">
<div style="display:flex; justify-content:flex-end; align-items:center;
gap:6px; margin-bottom:8px; flex-wrap:wrap;">
{# Bulk-fix: input cod + buton aplica (US-010 PRD 5.15) #}
<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);"
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-color:var(--accent);
font-size:13px; padding:4px 10px; border-radius:5px; cursor:pointer;"
aria-label="Aplica codul RAR la randurile blocate selectate">
Aplica cod
</button>
{# Separator vizual #}
<span style="color:var(--muted); font-size:11px; padding:0 2px;" aria-hidden="true">|</span>
{# Bulk-delete: pastreaza exact comportamentul existent #}
<button type="submit" id="bulk-sterge-btn"
style="background:var(--card); color:var(--err); border-color:var(--err);
font-size:13px; padding:4px 10px; border-radius:5px; cursor:pointer;">
Sterge selectate
</button>
</div>
{# Lista slim trimiteri (US-004, PRD 5.15).
Inlocuieste tabelul cu randuri compacte: VIN mono + operatie·ora + pill.
Nr. inmatriculare, data prestatie si nr. prezentare RAR raman accesibile
pe linia meta discreta (linia 3) si in modalul de detaliu. #}
<ul class="lista-trimiteri-slim" role="list"
aria-label="Lista trimiteri">
{% for r in rows %}
{# Randul slim: stanga = VIN mono scurt (L1) + operatie·ora muted (L2) + meta (L3);
dreapta = pill de stare. Click deschide modalul global (#detaliu-modal-body).
Clickabil/focusabil (role=button); Enter/Space deschid modalul (JS in base.html). #}
<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">
{# Zona checkbox — nu declanseaza modalul (stopPropagation).
Vizibila DOAR pe randurile gestionabile (error/needs_data/needs_mapping).
Latimea fixa previne reflow la prezenta/absenta checkbox-ului. #}
<div style="flex:0 0 22px; display:flex; align-items:center;" 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>
{# Bloc text principal — stanga, ocupa spatiul ramas. Rand de 2 linii (spec 5.16):
L1 = placuta (identificator primar); L2 = cod RAR · operatie · data prestatie. #}
<div style="flex:1 1 auto; min-width:0;">
{# Linia 1: nr. inmatriculare (placuta) — identificatorul primar pe care il
scaneaza operatorul. .slim-vin reumplut (acelasi nume de clasa, churn minim).
Fallback cand placuta lipseste ('—'): VIN scurt, apoi mesaj neutru
(nu randa em-dash izolat ca identificator). #}
{% 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 %}
{# Linia 2: cod RAR (sau 'nemapat') · operatie (ink, ellipsis) · data prestatie.
Separatorul "·" e injectat prin CSS intre celule. Operatia primeste ellipsis
ca randul sa NU treaca pe a 3-a linie nici la 390px.
VIN integral, #id_prezentare si secundele traiesc in modalul de detaliu. #}
<div class="slim-meta slim-rand2">
{% if r.prez.cod_rar and r.prez.cod_rar != '—' %}
<span class="cod-rar-cod">{{ r.prez.cod_rar }}</span>
{% else %}
<span class="cod-rar-cod cod-rar-sub muted">nemapat</span>
{% endif %}
<span class="slim-op">{{ r.prez.operatie }}</span>
{% if r.prez.data_prestatie and r.prez.data_prestatie != '—' %}
<span class="slim-data muted">{{ r.prez.data_prestatie }}</span>
{% endif %}
</div>
{# Micro-linie umana a problemei — text mic s-error, DOAR pe stari de problema
(loud-on-exception D6). Randul normal/finalizat ramane strict 2 linii.
Token tipografic --fs-xs (>=12px, scala 5.16). #}
{% if r.eticheta_problema and r.eticheta_problema != r.stare_scurt and r.eticheta_problema != r.stare_text %}
<div class="eticheta-problema s-error">{{ r.eticheta_problema }}</div>
{% endif %}
</div>
{# Zona dreapta: pill stare + badge mediu RAR (US-010 PRD 5.20) + buton Trimite (US-005) #}
<div style="flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:3px; white-space:nowrap;">
<span class="pill {{ r.stare_css }}" title="{{ r.stare_text }}">{{ r.stare_scurt }}</span>
{% if r.rar_env %}
{% set _eb = eticheta_env(r.rar_env) %}
<span class="{{ _eb[1] }}">{{ _eb[0] }}</span>
{% endif %}
{# US-005 (PRD 5.19 / D6): buton "Trimite" DOAR pe randurile tinute (queued AND held=1).
Afordanta dedicata cu stopPropagation (randul e role=button) — NU declanseaza modalul
de detaliu. Confirmare 1-linie prin hx-confirm (per rand); ireversibilitate in microcopy. #}
{% 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="margin-top:2px; 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>
</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 %}
<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">
&laquo;
</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)">
&laquo;
</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">
&raquo;
</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)">
&raquo;
</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 %}