feat(web): UI compact Trimiteri + bara bulk contextuala + fix buton "+" editor (PRD 5.22)
Trimiteri desktop = tabel grid cu header, un rand per trimitere: status = bulina cu tooltip, mediu = litera T/P, multi-cod = primul cod + badge +N, sub-linie de eroare cu textul brut RAR doar pe error/needs_data. Mobil: 2 linii prin grid-areas, fara header. Actiunile bulk apar doar la selectie (bara contextuala cu contor + texte explicite). Mapari: exemplu concret in loc de descriere la Reguli automate. Fix buton "+" chips (nu facea nimic, fara eroare): hx-disabled-elt="find button" mostenit de la form-ul parinte crapa htmx inainte de request -> hx-disabled-elt="this" explicit pe butoanele chips; separat, no-op-urile silentioase din post_form_chips au acum mesaje (cod invalid/duplicat) si confirmare cu chip evidentiat la succes. Suita: 1622 passed. Verify E2E in browser (desktop 1280 + mobil 390). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -359,6 +359,31 @@ def parse_erori(rar_error: object) -> list[dict]:
|
||||
return [{"problema": str(data)[:200], "cauza": "", "fix": "", "field": None}]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# eroare_bruta — textul compact al erorii pentru sub-linia randului de tabel
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Stari cu sub-linie de eroare pe rand. needs_mapping e EXCLUS intentionat:
|
||||
# "nemapat" e deja vizibil in coloana Cod RAR, sub-linia ar fi redundanta.
|
||||
_STARI_CU_EROARE_BRUTA = ("error", "needs_data")
|
||||
|
||||
|
||||
def eroare_bruta(status: str, rar_error: object) -> str:
|
||||
"""Textul brut, compact, al primei erori — pentru sub-linia randului.
|
||||
|
||||
Prefera cauza tehnica (mesajul serverului RAR, ex. ORA-12899) si cade pe
|
||||
problema. Sir gol in afara starilor error/needs_data. Nu arunca niciodata
|
||||
(reuse parse_erori, degradare gratioasa).
|
||||
"""
|
||||
if status not in _STARI_CU_EROARE_BRUTA:
|
||||
return ""
|
||||
erori = parse_erori(rar_error)
|
||||
if not erori:
|
||||
return ""
|
||||
e = erori[0]
|
||||
return (str(e.get("cauza") or "").strip() or str(e.get("problema") or "").strip())[:200]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Etichete stari preview import (vocabular DIFERIT de starile de submission)
|
||||
#
|
||||
|
||||
@@ -32,6 +32,7 @@ from ..web.csrf import get_csrf_token, verify_csrf
|
||||
from .labels import (
|
||||
ETICHETA_ULTIMA_AUTENTIFICARE_RAR,
|
||||
STARI_PREVIEW,
|
||||
eroare_bruta,
|
||||
eticheta_env,
|
||||
eticheta_rar,
|
||||
eticheta_scurta,
|
||||
@@ -1131,6 +1132,8 @@ def _submission_row_view(r) -> dict:
|
||||
"motiv": motiv,
|
||||
# eticheta umana scurta a problemei sub pill (text, nu cod brut).
|
||||
"eticheta_problema": _eticheta_problema(r["status"], motiv),
|
||||
# textul brut compact al erorii (mesaj RAR/validare) — sub-linia randului.
|
||||
"eroare_bruta": eroare_bruta(r["status"], r["rar_error"]),
|
||||
# randurile blocate (error/needs_data/needs_mapping) sunt selectabile pentru
|
||||
# stergere bulk; sent/sending/queued raman read-only (fara checkbox).
|
||||
"gestionabil": r["status"] in _GESTIONABILE_WEB,
|
||||
@@ -2397,9 +2400,19 @@ async def post_form_chips(request: Request) -> HTMLResponse:
|
||||
})
|
||||
|
||||
action = str(form.get("chips_action") or "").strip()
|
||||
chips_extra_error = False # T-C1/T-E4 (5.16): semnal pentru add_extra esuat
|
||||
# Orice adaugare are un efect vizibil: mesaj de refuz (chips_error) sau
|
||||
# confirmare + chip evidentiat (chips_ok / chips_added_cod). Fara no-op silentios.
|
||||
chips_error = ""
|
||||
chips_ok = ""
|
||||
chips_added_cod = ""
|
||||
|
||||
conn = get_connection()
|
||||
|
||||
def _cod_valid(cod: str) -> bool:
|
||||
return conn.execute(
|
||||
"SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (cod,)
|
||||
).fetchone() is not None
|
||||
|
||||
try:
|
||||
if action == "add":
|
||||
# Adauga cod la operatia specificata prin chips_add_op_index
|
||||
@@ -2408,44 +2421,37 @@ async def post_form_chips(request: Request) -> HTMLResponse:
|
||||
except (ValueError, TypeError):
|
||||
op_idx = 0
|
||||
add_cod = str(form.get(f"chips_add_cod_{op_idx}") or "").strip().upper()
|
||||
if add_cod and 0 <= op_idx < len(chips):
|
||||
exists = conn.execute(
|
||||
"SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
chips[op_idx]["cod_prestatie"] = add_cod
|
||||
|
||||
elif action == "add_flat":
|
||||
# Adauga cod nou in lista plata (fara op_service)
|
||||
add_cod_flat = str(form.get("chips_add_cod_flat") or "").strip().upper()
|
||||
if add_cod_flat:
|
||||
exists = conn.execute(
|
||||
"SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod_flat,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
chips.append({"cod_prestatie": add_cod_flat, "cod_op_service": "", "denumire": ""})
|
||||
|
||||
elif action == "add_extra":
|
||||
# Adauga cod RAR liber (extra, fara op_service) in modul operatii.
|
||||
# Refoloseste `chips_add_cod_flat` (acelasi select; dedup per-item pastrat).
|
||||
# Select gol sau cod invalid → chips_extra_error = True (semnal vizibil).
|
||||
add_cod_extra = str(form.get("chips_add_cod_flat") or "").strip().upper()
|
||||
if add_cod_extra:
|
||||
exists = conn.execute(
|
||||
"SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod_extra,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
# Dedup per-item (E4): nu adauga un chip extra identic deja existent
|
||||
existing_pairs = {
|
||||
(c.get("cod_op_service", ""), c.get("cod_prestatie", ""))
|
||||
for c in chips
|
||||
}
|
||||
if ("", add_cod_extra) not in existing_pairs:
|
||||
chips.append({"cod_prestatie": add_cod_extra, "cod_op_service": "", "denumire": ""})
|
||||
else:
|
||||
chips_extra_error = True # cod necunoscut in nomenclator
|
||||
if not add_cod:
|
||||
chips_error = "Selecteaza un cod RAR din lista inainte de a adauga."
|
||||
elif not (0 <= op_idx < len(chips)):
|
||||
chips_error = "Operatia nu mai exista in formular — reincarca editarea."
|
||||
elif not _cod_valid(add_cod):
|
||||
chips_error = f"Cod necunoscut in nomenclator: {add_cod}."
|
||||
else:
|
||||
chips_extra_error = True # select gol
|
||||
chips[op_idx]["cod_prestatie"] = add_cod
|
||||
chips_ok = f"{add_cod} adaugat."
|
||||
chips_added_cod = add_cod
|
||||
|
||||
elif action in ("add_flat", "add_extra"):
|
||||
# Adauga cod RAR liber (fara op_service): add_flat = mod plat,
|
||||
# add_extra = mod operatii. Acelasi select (chips_add_cod_flat),
|
||||
# aceeasi validare si acelasi dedup per-item (E4).
|
||||
add_cod_flat = str(form.get("chips_add_cod_flat") or "").strip().upper()
|
||||
if not add_cod_flat:
|
||||
chips_error = "Selecteaza un cod RAR din lista inainte de a adauga."
|
||||
elif not _cod_valid(add_cod_flat):
|
||||
chips_error = f"Cod necunoscut in nomenclator: {add_cod_flat}."
|
||||
else:
|
||||
existing_pairs = {
|
||||
(c.get("cod_op_service", ""), c.get("cod_prestatie", ""))
|
||||
for c in chips
|
||||
}
|
||||
if ("", add_cod_flat) in existing_pairs:
|
||||
chips_error = f"{add_cod_flat} este deja in lista — nu a fost adaugat inca o data."
|
||||
else:
|
||||
chips.append({"cod_prestatie": add_cod_flat, "cod_op_service": "", "denumire": ""})
|
||||
chips_ok = f"{add_cod_flat} adaugat."
|
||||
chips_added_cod = add_cod_flat
|
||||
|
||||
elif action == "remove":
|
||||
# Sterge codul de la indexul dat (lasa op_service intact -> operatie ramane nemapata)
|
||||
@@ -2480,7 +2486,9 @@ async def post_form_chips(request: Request) -> HTMLResponse:
|
||||
"has_r_odo": has_r_odo,
|
||||
"form_chips_url": "/form-chips",
|
||||
"chips_section_id": "chips-section",
|
||||
"chips_extra_error": chips_extra_error,
|
||||
"chips_error": chips_error,
|
||||
"chips_ok": chips_ok,
|
||||
"chips_added_cod": chips_added_cod,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
{% if chip.cod_op_service %}
|
||||
{% set _is_warn = chip.cod_prestatie in ('R-ODO', 'I-ODO') %}
|
||||
{% set _nemapat = not chip.cod_prestatie %}
|
||||
{% set _e_nou = (chips_added_cod is defined) and chips_added_cod and chip.cod_prestatie == chips_added_cod %}
|
||||
<div class="op-row {% if _nemapat %}op-row-warn{% endif %}" style="margin-bottom:6px;">
|
||||
<span class="op-row-name">
|
||||
{{ chip.cod_op_service }}
|
||||
@@ -59,11 +60,12 @@
|
||||
<span style="display:flex;align-items:center;gap:8px;">
|
||||
{% if chip.cod_prestatie %}
|
||||
{# ===== Operatie mapata: chip cu × ===== #}
|
||||
<span class="chip {% if _is_warn %}chip-warn{% endif %}"
|
||||
<span class="chip {% if _is_warn %}chip-warn{% endif %} {% if _e_nou %}chip-nou{% endif %}"
|
||||
aria-label="Prestatie {{ chip.cod_prestatie }} adaugata pentru {{ chip.cod_op_service }}">
|
||||
{{ chip.cod_prestatie }}
|
||||
<button type="button" class="chip-del"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -81,6 +83,7 @@
|
||||
<button type="button"
|
||||
style="font-size:10px;color:var(--muted);background:none;border:none;cursor:pointer;text-decoration:underline;padding:0;margin-left:4px;line-height:1;"
|
||||
hx-post="/trimitere/{{ _sub_id }}/salveaza-regula-chip"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#detaliu-modal-body"
|
||||
hx-swap="innerHTML"
|
||||
@@ -104,6 +107,7 @@
|
||||
<button type="button"
|
||||
class="add-code"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -126,11 +130,13 @@
|
||||
<div class="chips" role="group" aria-label="Coduri RAR suplimentare" style="margin-top:4px;">
|
||||
{% for chip in _extra_chips %}
|
||||
{% set _is_warn_extra = chip.cod_prestatie in ('R-ODO', 'I-ODO') %}
|
||||
<span class="chip {% if _is_warn_extra %}chip-warn{% endif %}"
|
||||
{% set _e_nou_extra = (chips_added_cod is defined) and chips_added_cod and chip.cod_prestatie == chips_added_cod %}
|
||||
<span class="chip {% if _is_warn_extra %}chip-warn{% endif %} {% if _e_nou_extra %}chip-nou{% endif %}"
|
||||
aria-label="Cod RAR suplimentar {{ chip.cod_prestatie }}">
|
||||
{{ chip.cod_prestatie }}
|
||||
<button type="button" class="chip-del"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -153,6 +159,7 @@
|
||||
<button type="button"
|
||||
class="add-code"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -167,24 +174,19 @@
|
||||
Nomenclator indisponibil — adaugarea de coduri suplimentare nu e posibila.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if chips_extra_error %}
|
||||
{# T-C1/T-E4 (5.16): semnal vizibil cand add_extra are select gol sau cod invalid #}
|
||||
<div class="chips-extra-error" style="font-size:11px;color:var(--err);padding:2px 0;" role="alert">
|
||||
Selecteaza un cod RAR din lista inainte de a adauga.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{# ===== Mod plat: lista de coduri libere (corectie pura, fara op_service) ===== #}
|
||||
<div class="chips" role="group" aria-label="Coduri RAR selectate">
|
||||
{% for chip in _chips %}
|
||||
{% if chip.cod_prestatie %}
|
||||
{% set _is_warn_flat = chip.cod_prestatie in ('R-ODO', 'I-ODO') %}
|
||||
<span class="chip {% if _is_warn_flat %}chip-warn{% endif %}"
|
||||
{% set _e_nou_flat = (chips_added_cod is defined) and chips_added_cod and chip.cod_prestatie == chips_added_cod %}
|
||||
<span class="chip {% if _is_warn_flat %}chip-warn{% endif %} {% if _e_nou_flat %}chip-nou{% endif %}"
|
||||
aria-label="Prestatie {{ chip.cod_prestatie }}">
|
||||
{{ chip.cod_prestatie }}
|
||||
<button type="button" class="chip-del"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -207,6 +209,7 @@
|
||||
<button type="button"
|
||||
class="add-code"
|
||||
hx-post="{{ _chips_url }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-include="closest form"
|
||||
hx-target="#{{ _sec_id }}"
|
||||
hx-swap="outerHTML"
|
||||
@@ -224,6 +227,18 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Rezultatul ultimei adaugari, in ambele moduri: refuz (rol=alert) sau confirmare.
|
||||
Clasa chips-extra-error pastrata pe eroare (selector stabil pentru teste/QA). #}
|
||||
{% if chips_error is defined and chips_error %}
|
||||
<div class="chips-extra-error" style="font-size:11px;color:var(--err);padding:2px 0;" role="alert">
|
||||
{{ chips_error }}
|
||||
</div>
|
||||
{% elif chips_ok is defined and chips_ok %}
|
||||
<div class="chips-adaugat-ok" style="font-size:11px;color:var(--ok);padding:2px 0;" role="status">
|
||||
{{ chips_ok }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Hint discret fara chips (debut) #}
|
||||
{% if not _chips %}
|
||||
<div style="font-size:10px;color:var(--muted);padding:4px 0;">
|
||||
|
||||
@@ -178,18 +178,19 @@
|
||||
<!-- ============================================================ -->
|
||||
<div class="card">
|
||||
<h2 style="font-size:15px; margin:0 0 8px;">Reguli automate (text)</h2>
|
||||
<p class="muted" style="margin:0 0 12px; font-size:13px; max-width:680px;">
|
||||
O regula leaga orice operatie al carei text <strong>contine</strong> (nu egal, ci substring)
|
||||
un cuvant de un cod RAR. Util pentru operatii fara cod intern: ex. orice operatie care
|
||||
<em>contine</em> „verificare" primeste codul ales. Match insensibil la majuscule/diacritice.
|
||||
</p>
|
||||
|
||||
{% if not text_rules %}
|
||||
<div class="empty" style="margin-bottom:12px;">
|
||||
Inca nu ai reguli. Ex: operatia contine «verificare» → OE-2.
|
||||
Mapeaza automat operatii similare fara cod intern. Adauga prima regula mai jos.
|
||||
{# Un exemplu concret in loc de descriere — arata direct formatul unei reguli. #}
|
||||
<div class="regula-exemplu"
|
||||
style="margin:0 0 12px; font-size:13.5px; max-width:680px; padding:10px 12px;
|
||||
border:1px dashed var(--line); border-radius:8px; background:var(--card2);">
|
||||
Daca operatia contine
|
||||
<span style="font-family:var(--font-mono); font-size:12.5px; padding:1px 6px; border-radius:4px;
|
||||
background:color-mix(in srgb, var(--warn) 18%, transparent); color:var(--warn);">verificare</span>
|
||||
<span class="muted" aria-hidden="true">→</span>
|
||||
Cod RAR =
|
||||
<span style="font-family:var(--font-mono); font-size:12.5px; font-weight:700; padding:1px 6px; border-radius:4px;
|
||||
background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--accent);">OE-2</span>
|
||||
Intretinere si revizie
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tablewrap tabel-card">
|
||||
<table>
|
||||
|
||||
@@ -60,45 +60,70 @@
|
||||
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 #}
|
||||
|
||||
{# 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);"
|
||||
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-color:var(--accent);
|
||||
font-size:13px; padding:4px 10px; border-radius:5px; cursor:pointer;"
|
||||
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 cod
|
||||
Aplica acest cod la <span class="bulk-n">0</span> <span class="bulk-n-txt">randuri</span>
|
||||
</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
|
||||
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>
|
||||
|
||||
{# Lista slim trimiteri.
|
||||
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. #}
|
||||
{# 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>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 %}
|
||||
{# 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). #}
|
||||
{# 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 }}"
|
||||
@@ -110,66 +135,61 @@
|
||||
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();">
|
||||
{# 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>
|
||||
|
||||
{# 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;">
|
||||
{# 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 %}
|
||||
|
||||
{# 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>
|
||||
{# 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 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 %}
|
||||
</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>
|
||||
|
||||
{# Zona dreapta: pill stare + badge mediu RAR + buton Trimite #}
|
||||
<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>
|
||||
{# 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="{{ _eb[1] }}">{{ _eb[0] }}</span>
|
||||
<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 %}
|
||||
{# 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. #}
|
||||
</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"
|
||||
@@ -180,7 +200,7 @@
|
||||
<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);
|
||||
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
|
||||
@@ -188,6 +208,12 @@
|
||||
</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>
|
||||
|
||||
@@ -741,35 +741,68 @@
|
||||
.compact-nr { font-size:var(--fs-xl); font-weight:700; line-height:1; }
|
||||
.compact-lbl { font-size:10px; color:var(--muted); margin-top:3px; white-space:nowrap; }
|
||||
}
|
||||
/* .lista-trimiteri-slim + .trimitere-slim — lista compacta cu separator --line2.
|
||||
Randul e clickabil (rol button), tinta min-height:44px pe mobil. */
|
||||
/* Bara contextuala bulk (5.22): ascunsa fara selectie; .activ (setat de JS
|
||||
cand >=1 checkbox e bifat) o afiseaza impreuna cu hint-ul de sub ea. */
|
||||
.bulk-bar { display:none; align-items:center; gap:8px; flex-wrap:wrap; font-size:13px;
|
||||
background:color-mix(in srgb, var(--accent) 10%, var(--card));
|
||||
border:1px solid color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
border-radius:8px; padding:8px 12px; margin-bottom:4px; }
|
||||
.bulk-bar.activ { display:flex; }
|
||||
.bulk-hint { display:none; font-size:12px; color:var(--muted); margin:0 0 8px; }
|
||||
.bulk-bar.activ + .bulk-hint { display:block; }
|
||||
|
||||
/* Tabel compact trimiteri (grid CSS, 5.22).
|
||||
Desktop: header de coloane + un rand per trimitere; sub-linia de eroare
|
||||
(.c-err) ocupa coloanele 2..-1 pe un al doilea rand de grid.
|
||||
Mobil (max-width:767px, mai jos): header ascuns, rand pe 2 linii. */
|
||||
.lista-trimiteri-slim { list-style:none; margin:0; padding:0; }
|
||||
.trimitere-slim { display:flex; align-items:center; justify-content:space-between; gap:12px;
|
||||
padding:14px 16px; border-bottom:1px solid var(--line2); min-height:44px; cursor:pointer; }
|
||||
.trimiteri-head, .trimitere-slim {
|
||||
display:grid; align-items:center; column-gap:10px; row-gap:2px;
|
||||
grid-template-columns:22px minmax(90px,150px) minmax(64px,auto) minmax(0,1fr) auto 46px 44px 84px; }
|
||||
.trimiteri-head { padding:2px 16px 8px; border-bottom:1px solid var(--line);
|
||||
font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
|
||||
color:var(--muted); }
|
||||
.trimiteri-head .th-c { text-align:center; }
|
||||
.trimitere-slim { padding:9px 16px; border-bottom:1px solid var(--line2);
|
||||
min-height:44px; cursor:pointer; }
|
||||
.trimitere-slim:last-child { border-bottom:none; }
|
||||
.trimitere-slim:hover { background:color-mix(in srgb, var(--accent) 6%, transparent); }
|
||||
.trimitere-slim:focus, .trimitere-slim:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
||||
.slim-vin { font-family:var(--font-mono); font-size:var(--fs-md); font-weight:600; color:var(--ink); }
|
||||
.slim-meta { font-size:var(--fs-sm); color:var(--muted); margin-top:3px; }
|
||||
/* Linia 2 a randului slim (5.16): cod RAR · operatie (ellipsis) · data, pe UN rand.
|
||||
Ellipsis-ul pe operatie garanteaza 2 linii MAX si la 390px. */
|
||||
.slim-rand2 { display:flex; align-items:baseline; gap:6px; min-width:0; }
|
||||
.slim-rand2 .cod-rar-cod { flex:0 0 auto; font-family:var(--font-mono); font-weight:600;
|
||||
color:var(--accent); }
|
||||
.slim-rand2 .cod-rar-cod.muted { color:var(--muted); font-weight:500; }
|
||||
.slim-rand2 .slim-op { flex:1 1 auto; min-width:0; white-space:nowrap; overflow:hidden;
|
||||
text-overflow:ellipsis; color:var(--ink); }
|
||||
.slim-rand2 .slim-data { flex:0 0 auto; }
|
||||
.slim-rand2 .slim-op::before, .slim-rand2 .slim-data::before {
|
||||
content:"·"; color:var(--muted); margin-right:6px; }
|
||||
.lista-trimiteri-slim .eticheta-problema { font-size:var(--fs-xs); line-height:1.3; margin-top:2px; }
|
||||
/* Pill slim (5.16): fill-tint + dot 7px + text colorat per stare (currentColor din .s-*).
|
||||
Pastrat pe FIECARE rand inclusiv Finalizat (linistit dar prezent). */
|
||||
.lista-trimiteri-slim .pill { display:inline-flex; align-items:center; gap:5px; font-weight:600;
|
||||
background:color-mix(in srgb, currentColor 14%, transparent);
|
||||
border-color:color-mix(in srgb, currentColor 35%, transparent); }
|
||||
.lista-trimiteri-slim .pill::before { content:""; width:7px; height:7px; border-radius:99px;
|
||||
background:currentColor; flex-shrink:0; }
|
||||
.trimitere-slim .c-check { display:flex; align-items:center; }
|
||||
.slim-vin { font-family:var(--font-mono); font-size:var(--fs-sm); font-weight:600; color:var(--ink);
|
||||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }
|
||||
/* Pe desktop copiii .c-meta devin celule de grid (cod / operatie / data). */
|
||||
.trimitere-slim .c-meta { display:contents; }
|
||||
.trimitere-slim .c-cod { white-space:nowrap; }
|
||||
.trimitere-slim .cod-rar-cod { font-family:var(--font-mono); font-weight:600;
|
||||
font-size:var(--fs-xs); color:var(--accent); }
|
||||
.trimitere-slim .cod-rar-cod.muted { color:var(--muted); font-weight:500; font-style:italic; }
|
||||
/* Badge +N: trimiterea are mai multe coduri RAR; toate stau in title. */
|
||||
.trimitere-slim .cod-plus { display:inline-block; font-family:var(--font-mono);
|
||||
font-size:10px; font-weight:700; color:var(--muted); border:1px solid var(--line);
|
||||
border-radius:99px; padding:0 5px; margin-left:4px; vertical-align:1px; cursor:default; }
|
||||
.trimitere-slim .c-op { min-width:0; white-space:nowrap; overflow:hidden;
|
||||
text-overflow:ellipsis; font-size:var(--fs-sm); color:var(--ink); }
|
||||
.trimitere-slim .c-data { font-size:var(--fs-xs); color:var(--muted); white-space:nowrap;
|
||||
font-variant-numeric:tabular-nums; }
|
||||
.trimitere-slim .c-status, .trimitere-slim .c-env { justify-self:center; line-height:1;
|
||||
display:inline-flex; align-items:center; }
|
||||
/* Bulina de status: doar cerc colorat (currentColor din .s-*); numele starii
|
||||
traieste in headerul coloanei + title/aria-label. */
|
||||
.dot-stare { display:inline-block; width:11px; height:11px; border-radius:99px;
|
||||
background:currentColor; }
|
||||
/* Litera de mediu: P = fill atentie (declaratie reala, ireversibila — L.142);
|
||||
T = contur discret (mediu de proba). */
|
||||
.env-l { display:inline-flex; align-items:center; justify-content:center;
|
||||
width:20px; height:20px; border-radius:99px; font-size:11px; font-weight:800;
|
||||
cursor:default; }
|
||||
.env-l-prod { background:color-mix(in srgb, var(--err) 80%, var(--card)); color:var(--card); }
|
||||
.env-l-test { color:var(--muted); border:1px solid var(--line); }
|
||||
.trimitere-slim .c-act { justify-self:end; }
|
||||
/* Sub-linia de eroare: text brut, mono, un singur rand cu ellipsis (title = complet). */
|
||||
.trimitere-slim .c-err { grid-column:2 / -1; min-width:0; font-family:var(--font-mono);
|
||||
font-size:var(--fs-xs); color:var(--err); white-space:nowrap; overflow:hidden;
|
||||
text-overflow:ellipsis; }
|
||||
/* .camp-slim — varianta compacta camp formular: label --fs-sm muted deasupra, input --fs-md, fundal --card2.
|
||||
Mono pentru campuri VIN/odometru/nr: adauga clasa .camp-mono pe input. */
|
||||
.camp-slim { margin-bottom:8px; }
|
||||
@@ -793,6 +826,8 @@
|
||||
.chip .chip-del:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
|
||||
/* Varianta chip warn (ex. R-ODO necesita odometruInitial) */
|
||||
.chip-warn { background:color-mix(in srgb, var(--warn) 22%, transparent); color:var(--warn); }
|
||||
/* Chip abia adaugat via /form-chips — confirmare vizibila a actiunii "+" */
|
||||
.chip-nou { outline:2px solid color-mix(in srgb, var(--ok) 45%, transparent); outline-offset:1px; }
|
||||
/* .add-code — buton dashed pentru adaugare cod in chipbox */
|
||||
.add-code { display:inline-flex; align-items:center; height:22px; padding:0 7px; background:transparent;
|
||||
border:1px dashed color-mix(in srgb, var(--accent) 55%, var(--line));
|
||||
@@ -806,9 +841,30 @@
|
||||
vizuala a randului de mapare op<->cod. */
|
||||
.op-row-name { font-size:var(--fs-sm); font-weight:700; color:var(--ink); }
|
||||
.op-row-warn { border-color:color-mix(in srgb, var(--warn) 45%, var(--line)); }
|
||||
/* Mobil: tinta touch pentru trimitere-slim (deja garantata prin min-height:44px in regula de baza) */
|
||||
/* Mobil: fara header, randul revine la 2 linii (placuta / cod·operatie·data),
|
||||
cu actiune + litera mediu + bulina status in dreapta. Grid-areas peste
|
||||
aceleasi celule; .c-meta redevine linie flex cu separatoare "·". */
|
||||
@media (max-width:767px) {
|
||||
.trimitere-slim { padding:12px 14px; }
|
||||
.trimiteri-head { display:none; }
|
||||
.trimitere-slim { padding:12px 14px; column-gap:8px;
|
||||
grid-template-columns:22px minmax(0,1fr) auto auto auto;
|
||||
grid-template-areas:
|
||||
"check veh act env status"
|
||||
"check meta act env status"
|
||||
"err err err err err"; }
|
||||
.trimitere-slim .c-check { grid-area:check; }
|
||||
.trimitere-slim .slim-vin { grid-area:veh; font-size:var(--fs-md); }
|
||||
.trimitere-slim .c-meta { grid-area:meta; display:flex; align-items:baseline;
|
||||
gap:6px; min-width:0; margin-top:3px; }
|
||||
.trimitere-slim .c-cod { flex:0 0 auto; }
|
||||
.trimitere-slim .c-op { flex:1 1 auto; }
|
||||
.trimitere-slim .c-data { flex:0 0 auto; }
|
||||
.trimitere-slim .c-op::before, .trimitere-slim .c-data:not(:empty)::before {
|
||||
content:"·"; color:var(--muted); margin-right:6px; }
|
||||
.trimitere-slim .c-status { grid-area:status; }
|
||||
.trimitere-slim .c-env { grid-area:env; }
|
||||
.trimitere-slim .c-act { grid-area:act; }
|
||||
.trimitere-slim .c-err { grid-area:err; }
|
||||
}
|
||||
/* === SENTINEL-COMPONENTE-SLIM: sfarsit componente slim === */
|
||||
/* === Fix mobil Mapari ===
|
||||
@@ -1222,6 +1278,34 @@
|
||||
if (saveBtn) saveBtn.classList.add('dirty');
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// Bara contextuala bulk (trimiteri): vizibila DOAR cu selectie.
|
||||
// Delegare pe document (supravietuieste swap-urilor HTMX); dupa un swap al
|
||||
// listei checkbox-urile se reseteaza, deci sync() ascunde bara automat.
|
||||
(function() {
|
||||
function syncBulkBar() {
|
||||
var form = document.getElementById('bulk-trimiteri');
|
||||
var bar = document.getElementById('bulk-bar');
|
||||
if (!form || !bar) return;
|
||||
var n = form.querySelectorAll('input[name="submission_id"]:checked').length;
|
||||
bar.classList.toggle('activ', n > 0);
|
||||
bar.querySelectorAll('.bulk-n').forEach(function(el) { el.textContent = n; });
|
||||
bar.querySelectorAll('.bulk-n-txt').forEach(function(el) {
|
||||
el.textContent = (n === 1 ? 'rand' : 'randuri');
|
||||
});
|
||||
}
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target && e.target.name === 'submission_id') syncBulkBar();
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!(e.target.closest && e.target.closest('#bulk-deselect'))) return;
|
||||
document.querySelectorAll('#bulk-trimiteri input[name="submission_id"]:checked')
|
||||
.forEach(function(cb) { cb.checked = false; });
|
||||
syncBulkBar();
|
||||
});
|
||||
document.body.addEventListener('htmx:afterSwap', syncBulkBar);
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
// Sub-taburi pagina Setari: comutare pur client-side, fara server.
|
||||
// Delegare pe document -> supravietuieste swap-urilor HTMX ale #card-cont (dupa
|
||||
|
||||
Reference in New Issue
Block a user