feat(5.9): US-003 - modal reutilizabil (overlay, focus-trap, a11y) + cleanup inline-expand 5.8

- base.html: #modal-detaliu (role=dialog, aria-modal) + #detaliu-modal-body swap target;
  focus-trap, inert+aria-hidden pe <main>, Esc/backdrop/x inchid, listener trimiteriChanged (R5/R7)
- _coada.html: ancora modal in afara #submissions-wrap; sters #trimitere-detaliu inert vechi
- _submissions.html: randul declanseaza modalul; sters tr.detaliu-rand sibling (R3)
- _trimitere_detaliu.html: script rescris pentru modal, fara marcheazaDetaliuDeschis/scrollIntoView (R4)
- teste: test_web_modal.py nou (3); test_web_detaliu_inline.py sters; test_acasa_trimiteri.py curatat (R3)
- gates: pytest PASS (suita completa 819). Browser E2E + design-review deferate la VERIFY.

Salvat manual: iteratiile Ralph 2-12 au ramas fara turns (30) inainte de commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-24 22:48:27 +00:00
parent 6d10f92452
commit fd4a05436d
10 changed files with 324 additions and 291 deletions

View File

@@ -1,15 +1,11 @@
{% from "_eroare.html" import card_erori %}
{% import '_macros.html' as ui %}
{# US-008: conectorul detaliului = fundal subtil + border-top pe randul-sibling
(.detaliu-rand, base.html), NU border-left accent (evita AI-slop). #}
<div class="card" id="detaliu-card-{{ id }}" style="border-color:var(--line);">
{# PRD 5.9 US-003: fragmentul se swap-uieste in corpul modalului global
(#detaliu-modal-body). Heading-ul poarta id-ul folosit de aria-labelledby al dialogului. #}
<div class="card" id="detaliu-card-{{ id }}" style="border:none; padding:0; margin:0;">
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 12px;">
<h2 style="font-size:15px; margin:0;">Detaliu trimitere #{{ id }}</h2>
<h2 id="detaliu-modal-titlu" style="font-size:15px; margin:0;">Detaliu trimitere #{{ id }}</h2>
<span class="pill {{ stare_css }}">{{ stare_text }}</span>
<button type="button" style="margin-left:auto; background:var(--card); color:var(--muted); border-color:var(--line);"
onclick="window.inchideDetaliu && window.inchideDetaliu('{{ id }}');">
Inchide
</button>
</div>
{% if stare_subtext %}
@@ -53,12 +49,12 @@
{% if gestionabil %}
<div style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line); display:flex; gap:10px; flex-wrap:wrap;">
<form hx-post="/trimitere/{{ id }}/repune"
hx-target="#detaliu-{{ id }}" hx-swap="innerHTML" style="margin:0;">
hx-target="#detaliu-modal-body" hx-swap="innerHTML" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit">Re-pune in coada</button>
</form>
<form hx-post="/trimitere/{{ id }}/sterge"
hx-target="#detaliu-{{ id }}" hx-swap="innerHTML"
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
hx-confirm="Stergi definitiv aceasta trimitere din coada?" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" style="background:var(--card); color:var(--err); border-color:var(--err);">
@@ -79,7 +75,7 @@
{% 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-{{ id }}" hx-swap="innerHTML"
<form hx-post="/trimitere/{{ id }}/mapeaza" hx-target="#detaliu-modal-body" hx-swap="innerHTML"
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 }}">
@@ -128,7 +124,7 @@
{% endif %}
<form hx-post="/trimitere/{{ id }}/corecteaza"
hx-target="#detaliu-{{ id }}" hx-swap="innerHTML">
hx-target="#detaliu-modal-body" hx-swap="innerHTML">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px 16px;">
@@ -157,20 +153,6 @@
</div>
{% endif %}
</div>
<script>
(function() {
/* US-008: detaliul traieste acum in randul-sibling #detaliu-{id}. Asiguram ca randul
de detaliu e vizibil (la re-swap dupa corectie/mapare HTMX poate readuce continut
intr-un container ascuns) si ca randul declansator e marcat ca deschis. Single-open
+ pauza poll sunt gestionate global in base.html. */
var cont = document.getElementById('detaliu-{{ id }}');
if (cont) {
var detRow = cont.closest('tr.detaliu-rand');
if (detRow) detRow.hidden = false;
cont.scrollIntoView({behavior: 'smooth', block: 'nearest'});
}
var rand = document.getElementById('trimitere-row-{{ id }}');
if (rand && window.marcheazaDetaliuDeschis) window.marcheazaDetaliuDeschis(rand);
})();
</script>
{# PRD 5.9 US-003 (R4): focus-ul dupa swap (incl. re-render corectie/mapare) e mutat in
corpul modalului din base.html (htmx:afterSettle pe #detaliu-modal-body). Vechiul script
inline (marcheazaDetaliuDeschis / scrollIntoView pe randul-sibling) a fost eliminat. #}