# PRD 5.10 — UX trimiteri (pill filtre, paginare, detaliu) + Mapari in meniu **Stare**: inchis (2026-06-25 — 14 stories + fix US-006b TDD prin echipa; VERIFY PASS; `/code-review high` 1 finding material reparat; regresie 896 passed / 1 skipped / 0 failed; fonturi IBM Plex reale) > Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar): `docs/api-rar-contract.md`. > Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead). ## 1. Obiectiv Curatare UX a dashboard-ului pe doua zone: (a) **tabelul de trimiteri** — pill-uri de filtrare pe categorii de problema in locul listei expandate de ID-uri, filtrare pe interval de data care chiar functioneaza pe timestamp-uri, paginare numerotata, VIN sub numarul de inmatriculare, editarea operatiei RAR si afisarea operatiei de service in detaliu, plus erori mai putin verbose in formularul de editare; (b) **Mapari** — mutata in meniul hamburger (fara tab-uri pe pagina principala), o singura pagina cu sectiunile consolidate si butoane de salvare/stergere vizibile. Pur UI/UX + un fix de filtrare backend. Backend-ul de trimitere (worker, masina de stari, idempotenta, mapping-rezolvare) ramane **NEATINS**. ## 2. Non-Goals (anti scope-creep) - **Nu** atingem worker-ul, masina de stari, idempotenta sau logica de mapare operatie→cod. - **Nu** permitem editarea operatiei RAR pe stari trimise/in curs (`sent`/`sending`/`queued`) — la RAR `FINALIZATA` e terminal; editarea apare doar pe `needs_data`/`needs_mapping`/`error`. - **Nu** schimbam paginarea client-side existenta din tabelele Mapari (datatable `data-dt`). - **Nu** schimbam mecanismul de persistenta/anti-FOUC al temei (PRD 5.3) — il extindem cu teme noi, nu il rescriem. - **Nu** adaugam filtre noi (doar pill-uri pe categoriile de problema deja existente + fix data). - **Nu** modificam contractul API `POST /v1/prezentari` / `GET /v1/prezentari` (operatia de service afisata vine din payload-ul deja stocat, nu cere camp nou). - **Nu** reproiectam meniul hamburger in sine — doar adaugam intrarea „Mapari" si scoatem tab-bar-ul. - **Acceptat explicit (review C4)**: doua idiomuri de paginare coexista — server-side numerotat pe Trimiteri (US-004) vs client-side `data-dt` (Inapoi/Inainte) pe Mapari. Diferenta e intentionata, nu „de reparat" ulterior. - **Acceptat explicit (review C2)**: butonul ciclic de tema (US-014) are cost de descoperire (4 teme fara optiuni vizibile). Compensat prin `aria-label` curenta+urmatoarea + tooltip; nu trecem la meniu/popover in aceasta livrabila. ## 3. Stories atomice > Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi > comportament = 2 stories. ### US-001: Fix filtrare pe interval de data (backend) **Ca** operator **vreau** ca filtrul „Data de la / pana la" sa returneze toate trimiterile din acea perioada **pentru ca** acum, fiindca `data_prestatie` poate avea ora/minut/secunda, comparatia de string exclude randurile si tabelul apare gol. - **Depinde de**: — - **Fisiere**: `app/web/routes.py` (`fragment_submissions`, `_is_iso_date`), `tests/test_web_filtre_submissions.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_web_filtre_submissions.py` — `test_filtru_data_include_timestamp_cu_ora`, `test_filtru_data_interval_inclusiv_capete`, `test_filtru_data_ignora_valori_ne_data` - **Acceptance criteria**: - [ ] **(must-fix, review C1)** Cauza reala = garda `_is_iso_date` cere `len(s)==10` (`routes.py:640-649`), deci orice `data_prestatie` cu ora e **exclusa inainte** de comparatie. Fix: introdu `_iso_date_prefix(value) -> str | None` (intoarce `value[:10]` daca parseaza ca `YYYY-MM-DD`, altfel `None`) si foloseste-l **atat** pentru garda **cat si** pentru comparatie. Nu modifica doar liniile de comparatie — altfel bug-ul ramane. - [ ] Filtrarea compara doar **portiunea de data** (primele 10 caractere, `YYYY-MM-DD`) a lui `data_prestatie`, chiar daca valoarea contine ora/minut/secunda (ex. `2026-06-20 14:35:07` sau `2026-06-20T14:35:07`). - [ ] Intervalul e **inclusiv la ambele capete**: `data_de <= data(rand) <= data_pana`. - [ ] O singura limita (doar `data_de` sau doar `data_pana`) functioneaza corect. - [ ] Valorile care nu incep cu o data ISO valida raman excluse din filtru (comportament actual pastrat). - [ ] `python3 -m pytest tests/test_web_filtre_submissions.py -q` trece. - **Verificare E2E**: browser pe `/` — import/seed cu o trimitere cu `data_prestatie` ce contine ora; filtru pe acea zi → randul apare. ### US-002: Expune operatia de service in view-ul de rand si detaliu (backend) **Ca** operator **vreau** sa vad operatia de service originala (codul intern / denumirea venita prin API sau import CSV) **pentru ca** vreau sa stiu ce a cerut service-ul, nu doar codul RAR mapat. - **Depinde de**: — - **Fisiere**: `app/payload_view.py` (sau `app/web/routes.py` `_detaliu_ctx`/`prezentare_din_payload`), `tests/test_payload_view.py` (~2 fisiere) - **Test intai (RED)**: `tests/test_payload_view.py` — `test_operatie_service_din_cod_op_service`, `test_operatie_service_din_denumire`, `test_fara_operatie_service_cand_lipseste` - **Acceptance criteria**: - [ ] **(must-fix, review M1)** `prezentare_din_payload` colapseaza acum service-op si RAR-op in chei suprapuse (`operatie = denumire or cod`, `cod_rar = cod_prestatie`; `payload_view.py:111-113`). Adauga **chei noi distincte** (ex. `op_service_cod` + `op_service_denumire`) din `cod_op_service`/`denumire`, separate de operatia RAR mapata. - [ ] **Conventie goala (must-fix, review M1)**: payload_view emite `EMPTY="—"` pentru valori lipsa (`payload_view.py:119-127`), dar US-007 cere „randul nu apare deloc (fara — gol)". Alege O conventie: fie campul nou intoarce `""`/`None` (nu `—`), fie template-ul testeaza fata de `'—'` (ca randul VIN, `_submissions.html:63`). Documenteaz-o aici si in US-007. - [ ] Cand payload-ul nu contine operatie de service (a venit direct cu `cod_prestatie`), campul e gol conform conventiei de mai sus, fara a arunca. - [ ] Helper-ul ramane pur (fara DB), defensiv la payload lipsa/corupt. - [ ] `python3 -m pytest tests/test_payload_view.py -q` trece. - **Verificare E2E**: `POST /v1/prezentari` cu `cod_op_service`+`denumire` → randul are operatia de service in context. ### US-003: Pill-uri de filtrare pe categorii de problema (UI) **Ca** operator **vreau** pill-uri „Date incomplete / Lipsa cod / Eroare" cu numar in sectiunea de filtrare, in loc de lista expandata `#40 TMB...3456 / B28ERR ...si inca 3` **pentru ca** lista de ID-uri e zgomotoasa; vreau sa apas un pill ca sa filtrez direct pe acea categorie. - **Depinde de**: — - **Fisiere**: `app/web/templates/_coada.html`, `app/web/templates/_status.html`, `app/web/routes.py` (counts per categorie pe fragmentul de filtre), `tests/test_web_pill_filtre.py` (~4 fisiere) - **Test intai (RED)**: `tests/test_web_pill_filtre.py` — `test_pill_per_categorie_cu_numar`, `test_pill_click_seteaza_status`, `test_fara_lista_id_uri` - **Acceptance criteria**: - [ ] In sectiunea de filtrare apar pill-uri: **Date incomplete** (`needs_data`), **Lipsa cod** (`needs_mapping`), **Eroare** (`error`), fiecare cu numarul total scoped pe cont; pill-ul lipseste/e ascuns cand numarul e 0. - [ ] **(must-fix, review M3/design-H1)** Lista de ID-uri si blocul `blocate_actionabil` sunt in **`_status.html:68-77`** (nu `_coada.html`), generate de `_blocate_actionabil` (`routes.py:562-594`). La eliminarea ID-urilor, scoate si codul mort care calcula `prezentare_din_payload`/`vin_partial` per rand. Pill-urile reutilizeaza contoarele deja calculate (`_status_counts`/`n`) — fara filtru backend nou. - [ ] **(must-fix, review design-M1)** Decide explicit soarta intregului bloc „Necesita atentia ta" + a contorului „Blocate": pill-urile **inlocuiesc** link-urile de categorie (nu triplu-encoda aceeasi informatie cu pill + link + dropdown). - [ ] **(must-fix, review M3/A3)** Pill-urile sunt elemente focalizabile reale (`