diff --git a/docs/prd/prd-5.15-propagare-design-dashboard-editare.md b/docs/prd/prd-5.15-propagare-design-dashboard-editare.md new file mode 100644 index 0000000..320c8d8 --- /dev/null +++ b/docs/prd/prd-5.15-propagare-design-dashboard-editare.md @@ -0,0 +1,271 @@ +# 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 `` 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.