14 stories TDD prin echipa de workeri (lead orchestreaza, 3 teammates pe valuri cu fisiere disjuncte; routes.py + base.html serializate ca fisiere fierbinti). - US-001 fix filtrare data (_iso_date_prefix pe garda+comparatie, prinde timestamp cu ora) - US-002/007 operatie service distincta in payload_view + afisare in detaliu - US-003 pill-uri categorii (button/aria-pressed; needs_mapping --warn, needs_data/error --err); fara lista ID-uri/dropdown - US-004 paginare numerotata 25/pag (total ramificat SQL-COUNT vs fetch-all+slice, clamp page, poll pastreaza pagina) - US-005 VIN block-level sub nr - US-006/006b editare cod RAR + validare nomenclator + recalcul idempotency (needs_data/needs_mapping via /corecteaza, error via /repune) - US-008 card eroare 3-niveluri doar pe read-only + rezumat top-of-form - US-009 Mapari in meniu hamburger; scoatere tab-bar + role=tablist orfan - US-010/011 pagina Mapari consolidata + butoane icon SVG + dirty-state (fara kebab/emoji) - US-012/012b header centrat + logo ROMFAST (/static/romfast_logo.png) in header - US-013 paleta azur ROMFAST (#2E74D6/#1F66C9) + IBM Plex Sans/Mono self-host (woff2 reale) - US-014 selector tema ciclic Light/Dark/Petrol/Auto + anti-FOUC pe 4 stari Backend trimitere (worker/masina stari/idempotenta/mapping) + schema NEATINSE (UI/UX pur + 1 fix de filtrare). VERIFY context curat PASS; /code-review high: 1 finding material reparat (US-006b). Regresie 896 passed, 1 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
264 lines
14 KiB
HTML
264 lines
14 KiB
HTML
{% from "_eroare.html" import card_erori %}
|
|
{% import '_macros.html' as ui %}
|
|
{# PRD 5.9 US-004: detaliu editabil in-place, butoane consolidate, ordine verticala R10.
|
|
Fragmentul se swap-uieste in corpul modalului global (#detaliu-modal-body). Heading-ul
|
|
poarta id-ul folosit de aria-labelledby al dialogului.
|
|
R9: operatie + cod RAR rezolvat apar IMPREUNA, read-only, folosind `prez.cod_rar`
|
|
(fallback „nemapat"), fara eticheta separata „Cod RAR". #}
|
|
{% set cod_afis = prez.cod_rar if (prez.cod_rar and prez.cod_rar != '—') else 'nemapat' %}
|
|
<div class="card" id="detaliu-card-{{ id }}" style="border:none; padding:0; margin:0;">
|
|
|
|
{# === R10 (1): header — #id + pill + motiv uman === #}
|
|
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 8px;">
|
|
<h2 id="detaliu-modal-titlu" style="font-size:15px; margin:0;">Detaliu trimitere #{{ id }}</h2>
|
|
<span class="pill {{ stare_css }}">{{ stare_text }}</span>
|
|
</div>
|
|
{% if motiv %}
|
|
<p class="muted" style="margin:0 0 12px; font-size:13px;">{{ motiv }}</p>
|
|
{% elif stare_subtext %}
|
|
<p class="muted" style="margin:0 0 12px; font-size:13px;">{{ stare_subtext }}</p>
|
|
{% endif %}
|
|
|
|
{# === R10 (2): bloc eroare blocanta — DOAR in read-only (US-008).
|
|
In editare, cardul 3-niveluri e inlocuit cu: erori per-camp in macro `camp`
|
|
(text simplu .s-error) + rezumat top-of-form pentru erori fara camp (mai jos). === #}
|
|
{% if not editabil and erori_3n %}
|
|
<div style="margin:0 0 14px;">
|
|
{{ card_erori(erori_3n) }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# === R10 (3) + R9: mapare inline (PRD 5.7) — alege cod RAR pentru operatiile nemapate.
|
|
Cand nemapate_inline, linia „Operatie: X · nemapat" apare in formularul de mai jos
|
|
(cod_afis = nemapat), iar aici e picker-ul; dupa mapare, re-render arata codul rezolvat. === #}
|
|
{% if nemapate_inline %}
|
|
<div style="margin:0 0 14px; padding-bottom:12px; border-bottom:1px solid var(--line);">
|
|
<h3 style="font-size:14px; margin:0 0 4px;">Mapeaza codul operatiei</h3>
|
|
<p class="muted" style="margin:0 0 10px; font-size:13px;">
|
|
Alege codul RAR pentru fiecare operatie. La salvare, randul se re-rezolva pe loc
|
|
(si celelalte randuri cu aceeasi operatie).
|
|
</p>
|
|
{% for op in nemapate_inline %}
|
|
{% set top = op.suggestions[0] if op.suggestions else None %}
|
|
{% set preselect = top.cod_prestatie if (top and top.score >= 60) else '' %}
|
|
<form hx-post="/trimitere/{{ id }}/mapeaza" hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
|
hx-disabled-elt="find button"
|
|
style="margin:0 0 12px; padding:10px; border:1px solid var(--line); border-radius:8px;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="cod_op_service" value="{{ op.cod_op_service }}">
|
|
<div style="margin-bottom:6px;">
|
|
<strong>{{ op.cod_op_service }}</strong>
|
|
{% if op.denumire and op.denumire != op.cod_op_service %}
|
|
<span class="muted">— {{ op.denumire }}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if op.suggestions %}
|
|
<div class="muted" style="font-size:12px; margin-bottom:6px;">
|
|
Sugestii:
|
|
{% for s in op.suggestions[:3] %}
|
|
<span class="sugg">{{ s.cod_prestatie }} ({{ s.score|round|int }}%)</span>{% if not loop.last %}, {% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
<div style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
|
<select name="cod_prestatie" required aria-label="Cod RAR pentru {{ op.cod_op_service }}"
|
|
style="flex:1; min-width:220px; max-width:380px;">
|
|
<option value="">— alege cod RAR —</option>
|
|
{% for n in nomenclator %}
|
|
<option value="{{ n.cod_prestatie }}" {% if n.cod_prestatie == preselect %}selected{% endif %}>
|
|
{{ n.cod_prestatie }} — {{ n.nume_prestatie }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
{{ ui.autosend_toggle(checked=True) }}
|
|
<button type="submit">Salveaza maparea</button>
|
|
</div>
|
|
</form>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# === R10 (4): formular editabil (needs_data/needs_mapping) SAU context read-only.
|
|
Zero dublare: campurile vehiculului apar O SINGURA DATA — editabile cand randul e
|
|
corectabil, altfel read-only. Operatie + cod RAR read-only deasupra campurilor. === #}
|
|
{% if editabil %}
|
|
{% set err_map = {} %}
|
|
{% for e in corectie_errors %}{% if e.field %}{% set _ = err_map.update({e.field: e.message}) %}{% endif %}{% endfor %}
|
|
|
|
{% if corectie_msg %}
|
|
<div class="flash" style="{% if corectie_error %}border-color:var(--err); background:color-mix(in srgb, var(--err) 12%, var(--card));{% endif %} margin:0 0 12px;"
|
|
{% if corectie_error %}role="alert"{% endif %}>{{ corectie_msg }}</div>
|
|
{% endif %}
|
|
|
|
{# US-008 (M6): erori fara camp (field None) nu dispar silentios in editare —
|
|
cardul 3n e ascuns, deci adaugam un rezumat simplu top-of-form.
|
|
Erori cu camp raman afisate per-camp de macro-ul `camp` de mai jos. #}
|
|
{% for e in erori_3n if not e.field %}
|
|
<div class="s-error" style="font-size:13px; margin:0 0 10px;" role="alert">{{ e.problema }}</div>
|
|
{% endfor %}
|
|
|
|
{% macro camp(nume, eticheta, valoare, tip='text') %}
|
|
<div style="margin-bottom:10px;">
|
|
<label for="c-{{ nume }}" class="muted" style="font-size:12px; display:block;">{{ eticheta }}</label>
|
|
<input id="c-{{ nume }}" type="{{ tip }}" name="{{ nume }}" value="{{ valoare }}"
|
|
style="width:100%; {% if err_map.get(nume) %}border-color:var(--err);{% endif %}"
|
|
{% if err_map.get(nume) %}aria-invalid="true"{% endif %}>
|
|
{% if err_map.get(nume) %}
|
|
<div class="s-error" style="font-size:12px; margin-top:2px;">{{ err_map.get(nume) }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
<form hx-post="/trimitere/{{ id }}/corecteaza"
|
|
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
|
hx-disabled-elt="find button">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
|
|
{# US-006: select cod RAR pe stari editabile (needs_data/needs_mapping), cu nomenclator.
|
|
Read-only pe sent/sending/queued/error (nomenclator_rar gol → ramura else). #}
|
|
{% if nomenclator_rar %}
|
|
<div style="margin:0 0 12px;">
|
|
<label for="c-cod-prestatie" class="muted" style="font-size:12px; display:block;">Operatie RAR (cod prestatie)</label>
|
|
{% if prez.operatie and prez.operatie != '—' %}
|
|
<div class="muted" style="font-size:12px; margin-bottom:4px;">{{ prez.operatie }}</div>
|
|
{% endif %}
|
|
<select id="c-cod-prestatie" name="cod_prestatie" style="max-width:380px; width:100%;"
|
|
aria-label="Alege operatia RAR din nomenclator">
|
|
<option value="">— pastrat ({{ cod_afis }}) —</option>
|
|
{% for n in nomenclator_rar %}
|
|
<option value="{{ n.cod_prestatie }}" {% if n.cod_prestatie == cod_prestatie_curent %}selected{% endif %}>
|
|
{{ n.cod_prestatie }} — {{ n.nume_prestatie }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% else %}
|
|
{# Operatie + cod RAR read-only deasupra campurilor (R9, fara eticheta „Cod RAR"). #}
|
|
<div style="margin:0 0 12px;">
|
|
<div class="muted" style="font-size:12px;">Operatie</div>
|
|
<div>{{ prez.operatie }} · {{ cod_afis }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# US-007: operatie service (cod intern + denumire venita prin API/import), distinct de
|
|
operatia RAR mapata. Conventie US-002: op_service_cod="" cand lipseste → randul absent. #}
|
|
{% if prez.op_service_cod %}
|
|
<div style="margin:0 0 12px;">
|
|
<div class="muted" style="font-size:12px;">Operatie service</div>
|
|
<div>{{ prez.op_service_cod }}{% if prez.op_service_denumire %} — {{ prez.op_service_denumire }}{% endif %}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Nr. inmatriculare pe rand propriu, VIN dedesubt — ambele latime plina. #}
|
|
{{ camp('nr_inmatriculare', 'Numar inmatriculare', form_nr) }}
|
|
{{ camp('vin', 'VIN (serie sasiu)', form_vin) }}
|
|
|
|
{# Restul campurilor in grila. #}
|
|
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:0 16px;">
|
|
{{ camp('data_prestatie', 'Data prestatie (YYYY-MM-DD)', form_data) }}
|
|
{{ camp('odometru_final', 'Odometru final', form_odo_final) }}
|
|
{{ camp('odometru_initial', 'Odometru initial (daca e cerut)', form_odo_initial) }}
|
|
</div>
|
|
|
|
{# === R10 (5): actiune primara conditionata de stare (R2). needs_data/needs_mapping
|
|
-> „Salveaza si retrimite" pe /corecteaza. UN SINGUR buton primar per stare. === #}
|
|
<div style="margin-top:14px;">
|
|
<button type="submit">Salveaza si retrimite</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
{# Context read-only pentru randuri ne-editabile (sent/sending/queued/error). #}
|
|
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:12px 24px;">
|
|
<div style="grid-column:1 / -1;">
|
|
<div class="muted" style="font-size:12px;">Numar inmatriculare</div><div>{{ prez.vehicul_nr }}</div>
|
|
</div>
|
|
<div style="grid-column:1 / -1;">
|
|
<div class="muted" style="font-size:12px;">VIN (serie sasiu)</div>
|
|
<div style="word-break:break-all;">{{ prez.vin }}</div>
|
|
</div>
|
|
<div><div class="muted" style="font-size:12px;">Operatie</div><div>{{ prez.operatie }} · {{ cod_afis }}</div></div>
|
|
{# US-007: operatie service (cod intern + denumire), distinct de operatia RAR.
|
|
Conventie US-002: op_service_cod="" cand lipseste → randul absent (fara "—"). #}
|
|
{% if prez.op_service_cod %}
|
|
<div><div class="muted" style="font-size:12px;">Operatie service</div>
|
|
<div>{{ prez.op_service_cod }}{% if prez.op_service_denumire %} — {{ prez.op_service_denumire }}{% endif %}</div></div>
|
|
{% endif %}
|
|
<div><div class="muted" style="font-size:12px;">Data prestatie</div><div>{{ prez.data_prestatie }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Odometru final</div><div>{{ prez.odometru }}</div></div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# === R10 (5): actiuni de jos — primar Re-pune (doar error) + Sterge pe RAND SEPARAT (R2/R11) === #}
|
|
{% if status == 'error' or gestionabil %}
|
|
<div class="detaliu-actiuni-jos" style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line);">
|
|
{# R2: error -> buton primar „Re-pune in coada" pe /repune (error nu e editabil pentru #}
|
|
{# campuri vehicul, dar US-006b permite schimbarea cod_prestatie prin acelasi formular). #}
|
|
{% if status == 'error' %}
|
|
<form hx-post="/trimitere/{{ id }}/repune"
|
|
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
|
hx-disabled-elt="find button" style="margin:0 0 10px;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
{# US-006b: select cod_prestatie optional in formularul /repune (doar pentru error). #}
|
|
{% if nomenclator_rar %}
|
|
<label for="cod-rar-error-{{ id }}" style="display:block; font-size:12px; color:var(--muted); margin-bottom:4px;">
|
|
Operatie RAR (optional — schimba codul si re-pune)
|
|
</label>
|
|
<select id="cod-rar-error-{{ id }}" name="cod_prestatie"
|
|
aria-label="Alege operatia RAR din nomenclator"
|
|
style="width:100%; margin-bottom:8px; font-size:13px;">
|
|
<option value="">— pastrat ({{ cod_prestatie_curent }}) —</option>
|
|
{% for item in nomenclator_rar %}
|
|
<option value="{{ item.cod_prestatie }}"
|
|
{% if item.cod_prestatie == cod_prestatie_curent %}selected{% endif %}>
|
|
{{ item.cod_prestatie }} — {{ item.nome_prestatie }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
<button type="submit">Re-pune in coada</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
{# R11: UN SINGUR Sterge, outline distructiv (var(--err)), pe rand separat, full-width pe mobil. #}
|
|
{% if gestionabil %}
|
|
<form hx-post="/trimitere/{{ id }}/sterge"
|
|
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
|
hx-disabled-elt="find button"
|
|
hx-confirm="Stergi definitiv trimiterea #{{ id }}? Nu se poate anula." style="margin:0;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn-sterge"
|
|
style="background:var(--card); color:var(--err); border-color:var(--err);">
|
|
Sterge
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# === R10 (6): Detalii tehnice — colapsat implicit === #}
|
|
<details style="margin-top:14px;">
|
|
<summary class="muted" style="font-size:12px; cursor:pointer;">Detalii tehnice</summary>
|
|
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px 24px; margin-top:10px;">
|
|
<div><div class="muted" style="font-size:12px;">Nr. prezentare RAR</div><div>{{ id_prezentare or '—' }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Cod HTTP RAR</div><div>{{ rar_status_code or '—' }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Reincercari</div><div>{{ retry_count }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Creat</div><div>{{ created_at }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Actualizat</div><div>{{ updated_at }}</div></div>
|
|
<div><div class="muted" style="font-size:12px;">Urmatoarea incercare</div><div>{{ next_attempt_at }}</div></div>
|
|
{% if erori_3n and erori_3n[0].cod %}
|
|
<div><div class="muted" style="font-size:12px;">Cod eroare (brut)</div><div>{{ erori_3n[0].cod }}</div></div>
|
|
{% endif %}
|
|
</div>
|
|
{% if rar_error %}
|
|
<div style="margin-top:10px;">
|
|
<div class="muted" style="font-size:12px;">Mesaj RAR (integral)</div>
|
|
<pre style="white-space:pre-wrap; word-break:break-all; font-size:12px; margin:4px 0 0; color:var(--muted);">{{ rar_error }}</pre>
|
|
</div>
|
|
{% endif %}
|
|
</details>
|
|
</div>
|
|
{# PRD 5.9 US-004 (R4): scriptul inline vechi (marcheazaDetaliuDeschis / scrollIntoView pe
|
|
randul-sibling) a fost eliminat de US-003. Focus-ul post-swap (incl. re-render corectie/
|
|
mapare) e gestionat de htmx:afterSettle pe #detaliu-modal-body din base.html. R5: inchiderea
|
|
modalului pe succes (queued/sterge) vine din HX-Trigger `inchideModal` emis de rute. #}
|