Files
rar-autopass/app/web/templates/_preview_rand.html
Claude Agent c9f9a1ca0e feat(5.16+5.17): tipografie/antet branded + tipuri cont, planuri si enforcement
PRD 5.16 — propagare design finalizata (system font stack, fara IBM Plex self-hostat):
- US-001/002/008: tokeni --font-ui/--font-mono (system stack) + scala --fs-*; zero
  @font-face si zero /static/fonts/; landing aliniat la acelasi stack
- US-003: RAR online = dot compact in antet + meniu burger; banda rosie DOAR pe blocat
  (invariant zero-silent-failures pastrat)
- US-010: antet "ROMFAST AUTOPASS" + nume service + /login brandeit 2 coloane + badge plan;
  meniu burger cu separatoare; gate strict pe is_authenticated
- US-011: selector tema pill icon+eticheta (reuse THEMES)
- US-004/005/006/007: bug-fix editor prestatii (picker cod+denumire, add_extra in mod
  operatii, cod ales se salveaza fara "+", Renunta inchide via closest)
- US-012/013: landing Autentificare->/login; wizard import colapsat + 4 pasi pe tokeni
- fix VERIFY E2E: contoare duplicate pe 390px (inline display:flex batea @media) -> CSS + test-lock

PRD 5.17 — tipuri de cont + trial Pro 30z + enforcement DUR:
- US-001/002/008: accounts.tier + trial_until (migrare aditiva defensiva); app/plans.py
  sursa unica (PLANS, FREE_MONTHLY_LIMIT=60, effective_tier(now injectabil), monthly_usage,
  CONSUMED_STATUSES); create_account trial Pro 30z; CLI set-tier (protejat id=1, audit)
- US-003/004/005: enforce volum 60/luna INAINTE de build_key pe ambele canale
  (PLAN_LIMITA_LUNARA, 3 niveluri + log_event); gate API Pro+ (PLAN_FARA_API 403 actionabil);
  valideaza/nomenclator raman permise; downgrade lazy; flag AUTOPASS_ENFORCE_PLANS (kill-switch)
- US-006: badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO
  pluralizat + banner one-time trial->Gratuit + pagina Cont

Regresie: 1380 passed, 0 failed, 1 deselected (live). E2E browser pe 390/1280 confirmat.
Backend trimitere (worker/masina stari/idempotenta/contract RAR) NEATINS. Lucrul 5.18
(corpus kNN) ramane separat, necomis.

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

114 lines
6.2 KiB
HTML

{#
_preview_rand.html — un singur rand de preview import.
US-006 (PRD 5.12): editarea inline (tr.preview-edit + mutual-exclusion script)
a fost eliminata. Butonul Editeaza deschide MODALUL global (#detaliu-modal-body).
Parametri:
editing — ELIMINAT (ignorat, pastrat pentru compatibilitate apeluri vechi)
include_oob — True: randeaza OOB rezumat + contor + script recalc (swap dupa save)
oob_tr — True: adauga hx-swap-oob pe <tr> insusi (pentru raspunsul POST succes)
summary — dict cu contoarele per status
Campuri pre-computate de _web_compute_preview (NOT din template raw):
row.prez — prezentare_din_payload(resolved): vehicul_nr, vin_scurt,
operatie, cod_rar, data_prestatie, odometru
row.stare_eticheta — text uman (ex. "Gata de trimis"), din STARI_PREVIEW
row.stare_css — clasa CSS (ex. "s-ok"), din STARI_PREVIEW
row.nota_umana — mesaj uman formatat pentru coloana Note (fara repr Python)
#}
{%- set res = row.resolved -%}
{%- set status = row.resolved_status -%}
{%- set _sent_dup = status in ('already_sent', 'duplicate_in_file') -%}
<tr id="preview-row-{{ row.row_index }}" data-status="{{ status }}"
{% if _sent_dup %}class="preview-sent-row"{% endif %}
{% if oob_tr %}hx-swap-oob="outerHTML:#preview-row-{{ row.row_index }}"{% endif %}
style="{% if status == 'needs_review' %}background:rgba(230,179,74,.04);{% elif _sent_dup %}opacity:.6;{% endif %}">
<td class="col-id muted" data-eticheta="#">{{ row.row_index + 1 }}</td>
<td class="col-stare" data-eticheta="Stare">
<span class="pill {{ row.stare_css }}" style="display:inline-flex; align-items:center; gap:5px;">
<span aria-hidden="true" style="display:inline-block; width:7px; height:7px; border-radius:99px; background:currentColor; flex-shrink:0;"></span>{{ row.stare_eticheta }}</span>
</td>
<td class="col-vehicul" data-eticheta="Vehicul">
{{ row.prez.vehicul_nr }}
{% if row.prez.vin_scurt and row.prez.vin_scurt != '—' %}
<div class="muted" style="font-size:12px; white-space:nowrap;">{{ row.prez.vin_scurt }}</div>
{% endif %}
</td>
<td class="col-operatie" data-eticheta="Operatie">
<div>{{ row.prez.operatie }}</div>
{% if row.prez.cod_rar and row.prez.cod_rar != '—' %}
<div class="cod-rar-sub"><span class="cod-rar-cod">{{ row.prez.cod_rar }}</span></div>
{% else %}
<div class="muted cod-rar-sub">nemapat</div>
{% endif %}
</td>
<td class="col-data" data-eticheta="Data prestatie">{{ row.prez.data_prestatie }}</td>
<td class="col-km" data-eticheta="KM final">{{ row.prez.odometru }}</td>
<td class="col-note" data-eticheta="Note"
style="font-size:var(--fs-xs); white-space:normal;">
{% if status == 'already_sent' and row.get('already_sent_info') %}
{% set ai = row.already_sent_info %}
deja trimis {{ (ai.get('created_at') or '')[:10] }}
{% if ai.get('id_prezentare') %}(#{{ ai.id_prezentare }}){% endif %}
{% elif status == 'duplicate_in_file' and row.get('duplicate_with') %}
dubla cu randul
{% for idx in row.duplicate_with %}{{ idx + 1 }}{% if not loop.last %}, {% endif %}{% endfor %}
{% else %}
{{ row.nota_umana or '' }}
{% endif %}
</td>
<td class="col-actiuni" data-eticheta="Actiuni" style="text-align:center;">
{% if status not in ('already_sent', 'duplicate_in_file') %}
<button type="button" class="btn-editeaza"
style="min-height:36px; padding:6px 14px; font-size:13px;
background:transparent; border-color:var(--line); color:var(--ink);"
hx-get="/_import/{{ import_id }}/rand/{{ row.row_index }}/editare-modal"
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
aria-label="Editeaza randul {{ row.row_index + 1 }} (VIN: {{ res.get('vin', '') }})">
Editeaza
</button>
{% endif %}
</td>
</tr>
{% if include_oob %}
{# OOB: actualizeaza rezumatul, contorul, bannerul needs_review dupa save/confirma-review. #}
{% set status_labels = [
('ok','Gata de trimis'), ('needs_review','Verifica valori'), ('needs_mapping','Cod RAR lipsa'),
('needs_data','Date incomplete'), ('already_sent','Deja trimis'), ('duplicate_in_file','Duplicat in fisier')] %}
<div id="preview-rezumat" hx-swap-oob="true"
style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px;">
{% for status_key, label in status_labels %}
{%- set cnt = summary.get(status_key, 0) -%}
{% if cnt > 0 %}<span class="pill s-{{ status_key }}" style="display:inline-flex; align-items:center; gap:5px; font-size:var(--fs-xs);"><span aria-hidden="true" style="display:inline-block; width:7px; height:7px; border-radius:99px; background:currentColor; flex-shrink:0;"></span>{{ cnt }} {{ label | lower }}</span>{% endif %}
{% endfor %}
</div>
<span id="preview-ok-count" hx-swap-oob="true" data-ok="{{ summary.get('ok', 0) }}" hidden></span>
{# Banner discoverability: OOB swap dupa confirmare/editare → dispare cand needs_review==0. #}
<div id="preview-needs-review-banner" hx-swap-oob="true">
{% if summary.get('needs_review', 0) %}
<div class="banner warn" role="note" aria-live="polite"
style="margin-bottom:12px; padding:8px 14px; border-radius:6px;
background:color-mix(in srgb, var(--warn, #e6b34a) 12%, var(--card));
border:1px solid var(--warn, #e6b34a); font-size:13px;">
Randurile cu <span class="pill s-needs_review" style="font-size:11px;">Verifica valori</span>
nu pleaca la RAR pana le deschizi in modal si confirmi in modal
cu butonul <strong>Confirma valorile</strong>.
</div>
{% endif %}
</div>
<script>
(function() {
/* Editare incheiata: re-activeaza confirm + butoanele Editeaza, recalculeaza N.
Defer pe tick-ul urmator: la momentul rularii scriptului, swap-ul randului poate
sa nu se fi asezat inca, deci tr[data-editing] ar fi inca prezent si updateN ar
lasa confirm dezactivat (editing=true). Dupa setTimeout(0) randul e in mod display. */
setTimeout(function() {
document.querySelectorAll('.btn-editeaza').forEach(function(b) { b.disabled = false; });
var btn = document.getElementById('confirm-btn');
if (btn) btn.title = '';
if (window.updateN) window.updateN();
}, 0);
})();
</script>
{% endif %}