Files
rar-autopass/docs/prd/prd-5.15-propagare-design-dashboard-editare.md
Claude Agent 822185e138 docs(prd): PRD 5.15 — propagare design landing in aplicatie
Dashboard compact (carduri-contor + lista slim), formular editare slim cu
VIN unic, Observatii (text liber = operatii service) si prestatii ca chips
multi-select. Propaga sistemul de design al landing-ului (teme/culori/carduri)
in controalele aplicatiei.

Decizii confirmate cu userul: D1 contoarele inlocuiesc bara de status; D2 teme
aditive (light/dark/petrol/Auto + grafit/cobalt/cupru/hartie); D3 chips reale
multi-cod; D4 contor Trimise all-time + luna + azi; D5 obs = denumirea
operatiilor de service (in payload, fara coloana noua; concatenat la import).

8 stories / 5 valuri, backend separat de UI. Draft, asteapta poarta de aprobare.

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

272 lines
19 KiB
Markdown
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.

# PRD 5.15 — Propagare design landing in aplicatie (dashboard compact + editare slim, VIN unic, prestatii multi-select)
**Stare**: draft
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Sistemul de design al landing-ului: `app/web/templates/landing.html` (commit 41aa385), `DESIGN.md`.
> Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`.
## 1. Obiectiv
Propagam sistemul de design al landing-ului comercial (carduri/liste/formulare compacte,
slim, si cele 4 teme grafit/cobalt/cupru/hartie) in aplicatia reala. Concret: dashboard-ul
Acasa primeste cardurile-contor + lista de trimiteri slim din mockup-ul hero, iar formularul
de editare trimitere primeste designul compact din mockup-ul "prestatie noua", cu **un singur
camp VIN**, **Observatii** ca text liber pentru operatiile de service si **prestatii ca chips
multi-select** de coduri RAR. Userul a cerut explicit replicarea acestor doua mockup-uri pentru
ca ii place cat de compacte/slim sunt.
Decizii de produs confirmate cu userul (poarta de aprobare a acestui PRD):
- **D1**: cardurile-contor INLOCUIESC bara de status actuala (`_status.html`); pastram doar
indicatorii de sanatate worker/RAR intr-o forma compacta.
- **D2**: temele sunt ADITIVE — pastram light/dark/petrol + Auto SI adaugam cele 4 din landing
(grafit/cobalt/cupru/hartie). Selectorul ciclic le parcurge pe toate. (grafit ~ dark si
hartie ~ light raman optiuni separate, la cererea userului.)
- **D3**: prestatiile sunt chips reale multi-select — utilizatorul poate adauga mai multe coduri
din nomenclatorul RAR si poate sterge oricare; se trimite lista `prestatii` completa (RAR
accepta lista `{codPrestatie, idPrezentare:null}``docs/api-rar-contract.md` §payload).
- **D4** (contor Trimise): cardul "Trimise" arata trei valori temporale — **all-time** (principal)
+ **luna asta** + **azi** (secundar). Necesita extinderea numaratorilor cu `sent_today`/`sent_month`.
- **D5** (Observatii = operatii service): in API-ul RAR, campul `obs` e DE FAPT denumirea operatiilor
din service. Deci `obs` = text liber cu operatiile efectuate; la import, daca fisierul nu are
coloana Observatii, **concatenam denumirea operatiei de service in `obs`**. `obs` ramane in
`payload_json` (camp din contractul RAR), fara coloana noua.
Fapte verificate care fundamenteaza scope-ul (nu presupuneri):
- `vin` la RAR e **un singur camp** (17 car., MAJUSCULE, fara O/I/Q) — cerinta "fara 2 campuri
VIN" e deja respectata azi (`_form_editare.html` are un singur `vin`); ramane sa NU regresam.
- `prestatii` e deja **lista** in modelul intern (`mapping.resolve_prestatii(prestatii: list[dict])`)
si in contractul RAR — multi-select nu cere model nou, ci editor nou.
- `obs` exista deja ca alias de coloana la import (`import_router.py:71` — Observatii/Obs/Mentiuni/Note)
si ca text liber optional in contractul RAR (`obs`); azi NU e editabil in formular.
## 2. Non-Goals (anti scope-creep)
- Fara modificari pe backend-ul de trimitere: worker, masina de stari, idempotenta-logica
(`build_key`), reconciliere, contract RAR. Recalcularea idempotentei la editare foloseste
mecanismul EXISTENT (ca la 3.5/5.10), nu unul nou.
- Fara migrare de schema decat daca strict necesar. `obs` si `prestatii` traiesc in
`submissions.payload_json` (de confirmat la US-005) — fara coloane noi daca payload-ul le poarta.
- Fara stergerea functionalitatii listei de trimiteri: filtre (data/vehicul/stare), paginare,
bulk-delete pe randuri blocate, click->detaliu raman; se schimba DOAR aspectul randului (slim).
- Fara schimbarea regulilor de mapare operatie->cod sau a validarii nomenclatorului RAR
(`mapping.py`, `validation.py` raman ca atare; doar callsite-urile de editare le folosesc cu lista).
- Fara redesign al landing-ului (deja livrat in 5.x); aici doar IMPORTAM stilul lui in app.
## 3. Stories atomice
> Backend + UI pentru acelasi comportament = 2 stories. `base.html` e fisier FIERBINTE
> (serializat intre valuri — un singur autor pe val). Toate UI verificate pe un esantion de teme (o tema luminoasa + una intunecata).
### US-001: Teme aditive (light/dark/petrol + grafit/cobalt/cupru/hartie) + tokeni `--card2`/`--line2`
**Ca** operator de service **vreau** aceleasi teme ca pe landing **pentru ca** aplicatia sa para
acelasi produs, coerent vizual.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_tema.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_tema.py``test_cele_4_teme_definite`, `test_tokeni_card2_line2_in_toate_temele`, `test_anti_fouc_4_stari`, `test_migrare_localStorage_legacy`
- **Acceptance criteria**:
- [ ] Pastram temele EXISTENTE light/dark/petrol si ADAUGAM 4 teme noi grafit/cobalt/cupru/hartie,
definite prin token-urile EXISTENTE (`--bg/--card/--ink/--muted/--line/--ok/--warn/--err/--accent`)
+ DOUA noi `--card2` (fundal input/contor) si `--line2` (separator subtire). `--card2`/`--line2`
primesc valori si in light/dark/petrol (fallback rezonabil). Maparea landing->app pentru cele 4
noi: `--text->--ink`, `--sub->--muted`, `--okt->--ok`, `--errt->--err`, `--infot->--accent`.
- [ ] Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru ->
hartie -> Auto, afiseaza eticheta temei curente, persistenta `localStorage` (D2).
- [ ] "Auto" pastrat: urmeaza `prefers-color-scheme`, rezolva la dark/grafit sau light/hartie
(decizie minora: Auto -> dark + hartie pentru light, sau dark/grafit — aliniaza cu I2).
- [ ] Script anti-FOUC in `<head>` seteaza `data-theme` sincron pre-paint pentru toate starile;
valoare necunoscuta -> Auto, fara blink. Valorile vechi raman valide (nu se mapeaza fortat).
- [ ] Contrast AA pentru text principal in toate temele (light + hartie sunt cele luminoase).
- [ ] `DESIGN.md` actualizat: sectiunea cromatica + selector tema reflecta toate temele.
- **Verificare E2E**: browser pe `/` (dashboard logat) — ciclare prin toate temele, persistenta la
refresh, fara FOUC; toate temele selectabile.
### US-002: Componente de design slim in `base.html` (CSS, fara consumatori inca)
**Ca** dezvoltator **vreau** clase reutilizabile pentru carduri-contor, lista slim, campuri slim si
chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu mockup-ul.
- **Depinde de**: US-001 (foloseste `--card2`/`--line2`)
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_web_responsive.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_web_responsive.py``test_clasa_contor_card`, `test_clasa_lista_slim`, `test_clasa_camp_slim`, `test_clasa_chips`
- **Acceptance criteria**:
- [ ] `.contor-card` (sau nume aliniat conventiei): cifra mare bold + eticheta mica muted, fundal
`--card2`, bordura `--line`, radius 8px, padding 10-12px; variante de culoare a cifrei prin
`.s-*` existente (verde/accent/rosu).
- [ ] `.lista-trimiteri-slim` cu rand `.trimitere-slim`: stanga = VIN mono (linia 1) + operatie·ora
muted (linia 2, 11px); dreapta = pill de stare; separator `--line2`; padding 10-14px.
Randul ramane clickabil (rol button) si pastreaza tinta 44px pe mobil.
- [ ] Varianta slim de camp formular: label 11px muted deasupra, input ~30px inaltime, fundal
`--card2`, mono pentru VIN/odometru/nr; integrata in macro-ul `camp` din `_macros.html`
printr-un flag (`slim=True`), fara a rupe randarea actuala (default neschimbat).
- [ ] `.chips` + `.chip` (cu buton `×` de stergere) pentru prestatii multi-select; accesibil
(buton real cu `aria-label`), stilat ca in mockup (accent 18%, font 10-11px).
- [ ] Zero regresie vizuala pe componentele existente (`.card/.pill/.act/.tabel-trimiteri`).
- **Verificare E2E**: pagina de proba/sandbox sau direct in US-003/004/007; vizual pe un esantion de teme + 390/1280.
### US-003: Dashboard Acasa — carduri-contor inlocuiesc bara de status
**Ca** operator **vreau** cele 3 carduri-contor compacte (Trimise / In coada / De corectat)
**pentru ca** sa vad starea dintr-o privire, ca in mockup.
- **Depinde de**: US-002
- **Fisiere**: `app/web/templates/_status.html`, `app/web/templates/_acasa.html`,
`app/web/routes.py` (`_status_counts` extins cu `sent_today`/`sent_month`), `tests/test_web_status.py`,
`tests/test_web_dashboard.py` (~5 fisiere)
- **Test intai (RED)**: `tests/test_web_status.py``test_trei_contoare_card`, `test_trimise_all_time_luna_azi`, `test_sanatate_compacta_worker_rar`, `test_fara_bara_veche`
- **Acceptance criteria**:
- [ ] Antetul Acasa = card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent),
**Trimise** (sent, verde), **De corectat** (blocate = needs_data + needs_mapping + error, rosu).
- [ ] Cardul **Trimise** afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta"
+ "azi" (sub-linie secundara). `_status_counts` extins cu `sent_today`/`sent_month` (filtru pe
`updated_at`/data trimitere; scoped pe cont), restul contoarelor din numaratoarea existenta.
- [ ] Indicatorii de sanatate worker/RAR + ultima autentificare RAR raman, intr-o forma compacta
(pill/glif), nu bara cu 2 randuri ca azi; pastreaza glifele accesibile ✓/✗ (nu doar culoare).
- [ ] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza.
- [ ] Scoped pe cont; poll-ul existent (`/_fragments/status`) randeaza noul antet fara a pierde tab-ul.
- [ ] Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow.
- **Verificare E2E**: browser pe `/` — contoare corecte vs date din DB, sanatate worker mort/viu,
poll pastreaza starea.
### US-004: Lista de trimiteri — rand slim (VIN + operatie·ora + pill)
**Ca** operator **vreau** lista de trimiteri in stil slim ca in mockup **pentru ca** e mai compacta
si mai usor de scanat, pastrand filtrele si actiunile.
- **Depinde de**: US-002
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/templates/_coada.html` (filtre raman),
`tests/test_web_submissions.py`, `tests/test_web_responsive.py` (~4 fisiere)
- **Test intai (RED)**: `tests/test_web_submissions.py``test_rand_slim_vin_operatie_pill`, `test_filtre_paginare_pastrate`, `test_bulk_doar_blocate`, `test_click_deschide_detaliu`
- **Acceptance criteria**:
- [ ] Fiecare rand: stanga VIN mono scurt (`vin_scurt`) linia 1 + operatie + ora/data muted linia 2;
dreapta pill de stare (`stare_css`/`stare_scurt`). Nr. inmatriculare, data completa si nr.
prezentare RAR raman accesibile (linie meta discreta si/sau in modalul de detaliu).
- [ ] Filtre (data/vehicul/stare — `_coada.html`), paginarea numerotata si bulk-delete pe randuri
blocate (checkbox doar pe `gestionabil`) raman FUNCTIONALE.
- [ ] Click pe rand deschide `/_fragments/trimitere/{id}` in modal (neschimbat).
- [ ] Slim layout consistent desktop si <=1024px (cardurile responsive existente nu regreseaza).
- [ ] Pill-urile de stare folosesc maparea din `labels.py` (zero etichete noi).
- **Verificare E2E**: browser — filtrare + paginare + click detaliu + bulk pe blocate, pe 4 teme,
pe 390/820/1280.
### US-005: Backend — `obs` (Observatii) editabil si persistat
**Ca** operator **vreau** sa editez Observatiile (operatiile de service in text liber) **pentru ca**
sa corectez/completez ce s-a facut, separat de codurile RAR.
- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (`/trimitere/{id}/corecteaza`),
`app/api/v1/import_router.py` (`/_import/{id}/rand/{row}/editeaza`, `EDIT_FIELDS`),
`app/validation.py` (obs optional), `app/payload_view.py` (echo obs),
`tests/test_web_corectie*.py`, `tests/test_import_review.py` (~6 fisiere)
- **Test intai (RED)**: `tests/test_web_corectie_obs.py``test_obs_editabil_persistat_corecteaza`, `test_obs_persistat_preview_editeaza`, `test_obs_optional_gol_ok`, `test_import_concateneaza_operatie_in_obs`
- **Acceptance criteria**:
- [ ] `obs` traieste in `payload_json` (camp `obs` din contractul RAR); fara coloana noua / migrare (D5).
- [ ] `obs` adaugat in `EDIT_FIELDS`; `corecteaza` si `editeaza` (preview) accepta si persista `obs`.
- [ ] `obs` optional (text liber, fara validare de continut, doar trim); apare in `payload_view`.
- [ ] `obs` se include in payload-ul trimis la RAR (camp `obs`) — fara a schimba celelalte campuri;
idempotenta se recalculeaza ca la orice editare (mecanism existent).
- [ ] **La import**, daca fisierul NU are coloana Observatii, denumirea operatiei de service se
CONCATENEAZA in `obs` (D5: `obs` = operatiile efectuate); daca are coloana Observatii, se
pastreaza textul ei. Format de concatenare definit (ex. denumiri separate prin "; ").
- **Verificare E2E**: `POST /trimitere/{id}/corecteaza` cu `obs` -> persistat -> vizibil in detaliu;
optional proba live RAR ca `obs` apare in FINALIZATA.
### US-006: Backend — prestatii multi-cod (lista) la editare/corectie
**Ca** operator **vreau** sa adaug/sterg mai multe coduri RAR pe o trimitere **pentru ca** o
comanda poate avea mai multe prestatii, asa cum accepta RAR.
- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (`/corecteaza`, `/repune`),
`app/api/v1/import_router.py` (`/editeaza`), `app/mapping.py` (folosit cu lista — fara schimbare
de logica), `app/validation.py` (fiecare cod in nomenclator), `tests/test_web_corectie*.py`,
`tests/test_mapping*.py` (~6 fisiere)
- **Test intai (RED)**: `tests/test_web_corectie_prestatii.py``test_mai_multe_coduri_acceptate`, `test_cod_invalid_respins`, `test_lista_goala_needs_mapping`, `test_idempotency_recalculat`, `test_odometru_initial_conditionat_R_ODO`
- **Acceptance criteria**:
- [ ] Handler-ele de editare accepta o LISTA de `cod_prestatie` (mai multe valori), inlocuind
selectul unic; reconstruiesc `prestatii` ca `[{cod_prestatie, idPrezentare:null}, ...]`.
- [ ] Fiecare cod e validat fata de nomenclator (`valid_codes`); cod necunoscut -> respins cu
mesaj (NU se trimite raw — invariant ORA-12899 din CLAUDE.md/contract).
- [ ] Lista goala de coduri -> ramane `needs_mapping` (nu se trimite fara cod).
- [ ] Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
- [ ] Se pastreaza regula `odometruInitial` obligatoriu cand lista contine `R-ODO`/`I-ODO`
(contract §payload) — validare existenta, doar verificata pe lista.
- **Verificare E2E**: `POST /corecteaza` cu 2 coduri valide -> `queued` cu `prestatii` de lungime 2;
cu un cod invalid -> respins; optional live RAR cu 2 prestatii -> FINALIZATA.
### US-007: UI — formular editare slim (VIN unic, Observatii, chips prestatii)
**Ca** operator **vreau** formularul de editare in design slim cu chips de prestatii **pentru ca**
e compact si imi arata clar codurile RAR si observatiile, ca in mockup.
- **Depinde de**: US-002, US-005, US-006
- **Fisiere**: `app/web/templates/_form_editare.html`, `app/web/templates/_macros.html`,
`app/web/templates/_trimitere_detaliu.html`, `app/web/templates/_editare_preview_modal.html`,
`tests/test_web_preview_edit.py`, `tests/test_web_detaliu*.py` (~6 fisiere)
- **Test intai (RED)**: `tests/test_web_form_editare_slim.py``test_un_singur_vin`, `test_camp_observatii_prezent`, `test_chips_multi_select_prestatii`, `test_adauga_sterge_chip`, `test_form_slim_in_ambele_modale`
- **Acceptance criteria**:
- [ ] Formularul foloseste varianta slim de camp (US-002): VIN, Data prestatiei, Nr. inmatriculare,
Observatii (textarea), prestatii (chips), Odometru — un SINGUR camp VIN (fara "Confirma VIN").
- [ ] Observatii = textarea liber, legat de `obs` (US-005).
- [ ] Prestatii = chips multi-select: fiecare cod ca chip cu `×`; un picker (dropdown din
nomenclator) adauga un cod nou; lista se trimite ca `cod_prestatie` multiplu (US-006).
- [ ] Acelasi `_form_editare.html` slujeste ambele modale (detaliu `/corecteaza` si preview
`/editeaza`), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select).
- [ ] Stilizare fidela mockup-ului pe cele 4 teme; tinte 44px pe mobil; a11y (label-uri, aria).
- **Verificare E2E**: browser — editare trimitere needs_data: schimb VIN + scriu Observatii + adaug
2 coduri RAR (chips) + sterg unul -> salvare -> persistat; identic in preview import.
### US-008: Teste de regresie + E2E final pe cele 4 teme
**Ca** dezvoltator **vreau** acoperire si o trecere E2E completa **pentru ca** redesign-ul atinge
fisiere fierbinti (base.html) si nu vreau regresii pe teme/liste/formular.
- **Depinde de**: US-003, US-004, US-007
- **Fisiere**: `tests/test_web_responsive.py`, `tests/test_tema.py`, `tests/test_web_submissions.py`
(~3 fisiere)
- **Test intai (RED)**: completare scenarii lipsa (4 teme x componente noi; slim list desktop+mobil)
- **Acceptance criteria**:
- [ ] `pytest -q -m "not live"` verde (fara regresii fata de baseline).
- [ ] E2E Playwright pe 390/820/1280, pe light/dark/petrol + cele 4 noi (esantion: grafit + hartie): dashboard contoare,
lista slim cu filtre/paginare/bulk, formular slim cu chips, fara overflow orizontal.
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
## 4. Riscuri
- **base.html fisier fierbinte**: US-001/US-002 il ating amandoua + US-003/004/007 il citesc.
Serializeaza pe valuri (un singur autor pe val pe base.html), ca la 5.12/5.13.
- **Migrare teme legacy**: useri cu `localStorage.theme` = light/dark/petrol. Mitigare: maparea
grafioasa din US-001 (light->hartie, dark->grafit, petrol->grafit) + test dedicat.
- **Restyle lista = pierdere de functii**: filtre/paginare/bulk pot fi sparte de schimbarea de
markup. Mitigare: US-004 are AC explicite pentru pastrarea lor + teste lock.
- **Idempotenta la prestatii multiple**: schimbarea listei schimba cheia canonica. Mitigare:
refolosim mecanismul existent de recalcul + prindere coliziune (3.5/5.10), zero logica noua.
- **Densitate vizuala pe mobil**: randul slim cu 2 linii + pill poate aglomera. Mitigare: tinte
44px + verificare 390px in US-004/008.
## 5. Intrebari deschise
> Toate intrebarile au fost REZOLVATE cu userul (vezi D1-D5 §1). Pastrate aici ca istoric al deciziei.
- **I1 — contor Trimise** [REZOLVAT]: arata all-time + luna asta + azi (D4). `_status_counts` extins.
- **I2 — teme** [REZOLVAT]: aditiv — light/dark/petrol + Auto + grafit/cobalt/cupru/hartie (D2).
- **I3 — stocare obs** [REZOLVAT]: in `payload_json`, fara coloana noua (D5).
- **I4 — operatii la import -> obs** [REZOLVAT]: concatenam denumirea operatiei in `obs` cand
fisierul nu are coloana Observatii (D5).
- Reziduale minore (de decis la executie, non-blocante): formatul exact de concatenare a denumirilor
in `obs`; rezolvarea "Auto" la light vs hartie; eventuala deduplicare grafit~dark / hartie~light
in eticheta selectorului.
## 6. Valuri de executie (graful de dependente)
```
Val 1: [US-001] base.html teme + tokeni (autor unic pe base.html)
Val 2: [US-002] base.html componente (dupa US-001, autor unic pe base.html)
Val 3: [US-003] [US-004] dashboard + lista (consuma US-002; fisiere disjuncte) ||
[US-005] [US-006] backend obs + prestatii (fisiere backend; pot rula in paralel cu UI)
Val 4: [US-007] formular slim (dupa US-002+US-005+US-006)
Val 5: [US-008] regresie + E2E final
```
---
## Raport VERIFY
> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.
> PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E pe RAR test). Lipseste pana la VERIFY.