curatare
This commit is contained in:
@@ -1,609 +0,0 @@
|
||||
{
|
||||
"projectName": "mobile-ux-improvements",
|
||||
"branchName": "ralph/bulk-receipt-upload",
|
||||
"description": "Corectarea comportamentului de refresh pentru bulk upload, implementarea selecției multiple pe mobil cu interfață Android nativă, afișarea numelui fișierului pentru toate bonurile, și rezolvarea bug-urilor de UX raportate.",
|
||||
"cssRules": {
|
||||
"documentation": [
|
||||
"docs/ONBOARDING_CSS.md",
|
||||
"docs/DESIGN_TOKENS.md",
|
||||
"docs/CSS_PATTERNS.md"
|
||||
],
|
||||
"goldenRules": [
|
||||
"Folosește DOAR design tokens - NICIODATĂ valori hardcodate",
|
||||
"Verifică CSS_PATTERNS.md înainte de a scrie CSS nou",
|
||||
"Testează în AMBELE teme (light + dark mode)",
|
||||
"NICIODATĂ :deep() în componente (PrimeVue → vendor/)",
|
||||
"NICIODATĂ duplicate CSS (write once, use everywhere)"
|
||||
],
|
||||
"mobileLayoutTokens": {
|
||||
"topBarHeight": "56px",
|
||||
"bottomNavHeight": "56px",
|
||||
"fabSize": "56px",
|
||||
"fabBottomOffset": "72px",
|
||||
"touchTargetMin": "48px"
|
||||
},
|
||||
"selectionModeColors": {
|
||||
"selected": {
|
||||
"background": "var(--blue-50)",
|
||||
"border": "var(--blue-500)"
|
||||
},
|
||||
"selectedDark": {
|
||||
"background": "var(--blue-900)",
|
||||
"border": "var(--blue-400)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Backend - Stocare Batch și Processing Status",
|
||||
"description": "Ca developer, vreau să extind schema Receipt pentru a stoca informații de batch, pentru că am nevoie de persistență pentru tracking.",
|
||||
"priority": 1,
|
||||
"acceptanceCriteria": [
|
||||
"Câmpuri noi în tabelul receipts: batch_id, processing_status, processing_error, file_hash, processing_started_at, processing_completed_at",
|
||||
"Index pe batch_id, file_hash, processing_status",
|
||||
"Migration reversibilă cu Alembic"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Backend - Endpoint List cu Batch Info",
|
||||
"description": "Ca developer, vreau să extind endpoint-ul GET /receipts pentru a include info de batch.",
|
||||
"priority": 2,
|
||||
"acceptanceCriteria": [
|
||||
"Response include câmpurile de batch și processing pentru fiecare receipt",
|
||||
"Filtrare pe processing_status și batch_id funcționează",
|
||||
"Response include processing_stats"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Backend - Reject Automat pentru Duplicate (File Hash)",
|
||||
"description": "Ca sistem, vreau să detectez și să reject fișierele duplicate la upload.",
|
||||
"priority": 3,
|
||||
"acceptanceCriteria": [
|
||||
"SHA-256 hash pentru duplicate detection",
|
||||
"Response include existing_receipt_id pentru duplicates"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Frontend - Drag Anywhere pentru Upload",
|
||||
"description": "Ca utilizator, vreau să pot trage fișiere oriunde pe pagina de bonuri.",
|
||||
"priority": 4,
|
||||
"acceptanceCriteria": [
|
||||
"DragDropOverlay.vue componentă",
|
||||
"Global listeners cleanup în onUnmounted"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Frontend - Row Grouping per Batch în DataTable",
|
||||
"description": "Ca utilizator, vreau să văd bonurile din același batch grupate vizual.",
|
||||
"priority": 5,
|
||||
"acceptanceCriteria": [
|
||||
"BatchGroupHeader.vue componentă",
|
||||
"Grupuri sortate după processing_started_at descending"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Frontend - Coloană Status Procesare în Tabel",
|
||||
"description": "Ca utilizator, vreau să văd statusul fiecărui bon din batch într-o coloană dedicată.",
|
||||
"priority": 6,
|
||||
"acceptanceCriteria": [
|
||||
"ProcessingStatusCell.vue componentă",
|
||||
"Status updates în real-time via polling"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Frontend - Mesaj Eroare Vizibil în Listă",
|
||||
"description": "Ca utilizator, vreau să văd mesajul de eroare direct în listă.",
|
||||
"priority": 7,
|
||||
"acceptanceCriteria": [
|
||||
"Truncated error message cu tooltip pentru full text"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Frontend - Quick Filter Chips pentru Statusuri Procesare",
|
||||
"description": "Ca utilizator, vreau filtre rapide pentru bonurile cu erori sau în procesare.",
|
||||
"priority": 8,
|
||||
"acceptanceCriteria": [
|
||||
"Chips 'În procesare (N)' și 'Cu erori (N)' în status bar"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Frontend - Lock Row în Procesare (Read-Only)",
|
||||
"description": "Ca utilizator, vreau ca bonurile în procesare să fie read-only.",
|
||||
"priority": 9,
|
||||
"acceptanceCriteria": [
|
||||
"Butoane și checkbox disabled pentru pending/processing",
|
||||
"Tooltip pe butoane dezactivate"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Frontend - Retry Individual și Retry All Failed",
|
||||
"description": "Ca utilizator, vreau să pot re-procesa bonurile cu erori.",
|
||||
"priority": 10,
|
||||
"acceptanceCriteria": [
|
||||
"Buton Reîncercă per rând failed",
|
||||
"Buton Reîncercă toate erorile în BatchGroupHeader"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Frontend - Auto-Resume Polling la Refresh/Revenire",
|
||||
"description": "Ca utilizator, vreau ca procesarea să continue când revin.",
|
||||
"priority": 11,
|
||||
"acceptanceCriteria": [
|
||||
"localStorage pentru active batch IDs",
|
||||
"Auto-resume polling la onMounted"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-012",
|
||||
"title": "Backend - Auto-Cleanup Erori După 7 Zile",
|
||||
"description": "Ca sistem, vreau să șterg automat bonurile cu erori după 7 zile.",
|
||||
"priority": 12,
|
||||
"acceptanceCriteria": [
|
||||
"Background job pentru cleanup",
|
||||
"Șterge receipts și attachments"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-013",
|
||||
"title": "Cleanup - Eliminare Pagină Separată Bulk Upload",
|
||||
"description": "Ca developer, vreau să elimin pagina separată de bulk upload.",
|
||||
"priority": 13,
|
||||
"acceptanceCriteria": [
|
||||
"Route redirect pentru backwards compatibility",
|
||||
"Șterge componente nefolosite"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-014",
|
||||
"title": "Backend - Endpoint Cancel Job Individual",
|
||||
"description": "Ca sistem, vreau un endpoint API pentru anularea unui job specific de procesare.",
|
||||
"priority": 14,
|
||||
"acceptanceCriteria": [
|
||||
"POST /api/data-entry/bulk/cancel/{job_id} endpoint creat",
|
||||
"Job-uri cu status completed/failed returnează 400 Bad Request",
|
||||
"Job-uri pending/processing sunt marcate cancelled",
|
||||
"Response include: {success, job_id, cancelled_at, message}"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-015",
|
||||
"title": "Backend - Endpoint Cancel Batch Complet",
|
||||
"description": "Ca sistem, vreau un endpoint API pentru anularea tuturor job-urilor dintr-un batch.",
|
||||
"priority": 15,
|
||||
"acceptanceCriteria": [
|
||||
"POST /api/data-entry/bulk/cancel-batch/{batch_id} endpoint creat",
|
||||
"Response include: {success, batch_id, cancelled_count, skipped_count, message}"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-016",
|
||||
"title": "Frontend - Store Actions pentru Cancel",
|
||||
"description": "Ca dezvoltator, vreau acțiuni în batchProgressStore pentru cancel.",
|
||||
"priority": 16,
|
||||
"acceptanceCriteria": [
|
||||
"batchProgressStore.cancelJob(jobId) implementat",
|
||||
"batchProgressStore.cancelBatch(batchId) implementat"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-017",
|
||||
"title": "Frontend - Afișare Jobs Pending în Tabel",
|
||||
"description": "Ca utilizator, vreau să văd fișierele încărcate imediat în tabel.",
|
||||
"priority": 17,
|
||||
"acceptanceCriteria": [
|
||||
"După upload success, rândurile pentru jobs apar instant în tabel",
|
||||
"Tabelul poate randa atât Receipt-uri cât și BatchJob-uri"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-018",
|
||||
"title": "Frontend - Tranziție Job → Receipt când OCR Termină",
|
||||
"description": "Ca sistem, vreau ca rândul de job să se transforme în receipt când OCR termină.",
|
||||
"priority": 18,
|
||||
"acceptanceCriteria": [
|
||||
"Când polling detectează job completed cu receipt_id, rândul se actualizează",
|
||||
"Tranziția e smooth - rândul NU dispare și reapare"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-019",
|
||||
"title": "Frontend - Animație Status Change",
|
||||
"description": "Ca utilizator, vreau o indicație vizuală când un fișier își schimbă statusul.",
|
||||
"priority": 19,
|
||||
"acceptanceCriteria": [
|
||||
"Badge-ul de status se schimbă cu CSS transition opacity 300ms",
|
||||
"Highlight verde/roșu subtil pentru completed/failed"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-020",
|
||||
"title": "Frontend - Buton Cancel Individual pe Row",
|
||||
"description": "Ca utilizator, vreau un buton Cancel pe fiecare fișier pending/processing.",
|
||||
"priority": 20,
|
||||
"acceptanceCriteria": [
|
||||
"Fișierele pending/processing au icon Cancel (×)",
|
||||
"După cancel success, rândul dispare cu fade-out 300ms"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-021",
|
||||
"title": "Frontend - Buton Cancel All în BatchGroupHeader",
|
||||
"description": "Ca utilizator, vreau un buton pentru a anula toate fișierele dintr-un batch.",
|
||||
"priority": 21,
|
||||
"acceptanceCriteria": [
|
||||
"BatchGroupHeader are buton 'Anulează tot' pentru batch-uri cu pending/processing jobs",
|
||||
"Job-urile completed/failed rămân vizibile"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-022",
|
||||
"title": "Frontend - Checkbox Disabled pentru Jobs în Procesare",
|
||||
"description": "Ca utilizator, vreau ca checkbox-urile să fie disabled pentru fișiere în procesare.",
|
||||
"priority": 22,
|
||||
"acceptanceCriteria": [
|
||||
"Checkbox-ul este disabled pentru rânduri de tip job",
|
||||
"Select All NU include job-urile în procesare"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-023",
|
||||
"title": "Frontend - Restore Jobs la Refresh/Revenire",
|
||||
"description": "Ca utilizator, vreau să văd job-urile pending când revin pe pagină.",
|
||||
"priority": 23,
|
||||
"acceptanceCriteria": [
|
||||
"La onMounted, verifică localStorage pentru active batch IDs",
|
||||
"Job-urile pending/processing apar în tabel"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-024",
|
||||
"title": "Backend - Endpoint Bulk Delete",
|
||||
"description": "Ca frontend, vreau să pot trimite o listă de ID-uri pentru ștergere.",
|
||||
"priority": 24,
|
||||
"acceptanceCriteria": [
|
||||
"DELETE /api/data-entry/receipts/bulk acceptă body: { ids: [...] }",
|
||||
"Returnează partial success response"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-025",
|
||||
"title": "Frontend - Buton Șterge în Bulk Actions Bar",
|
||||
"description": "Ca utilizator, vreau să văd un buton 'Șterge' când am selecții.",
|
||||
"priority": 25,
|
||||
"acceptanceCriteria": [
|
||||
"Butonul 'Șterge' apare în bulk actions bar când selectedReceipts.length > 0",
|
||||
"Butonul are icon pi-trash și severity danger"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-026",
|
||||
"title": "Frontend - Dialog Confirmare Ștergere Bulk",
|
||||
"description": "Ca utilizator, vreau o confirmare înainte de ștergere.",
|
||||
"priority": 26,
|
||||
"acceptanceCriteria": [
|
||||
"La click pe 'Șterge', apare dialog cu mesaj confirmare",
|
||||
"Dialog-ul folosește PrimeVue ConfirmDialog"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-027",
|
||||
"title": "Frontend - Bulk Delete cu Partial Success Toast",
|
||||
"description": "Ca utilizator, vreau să văd rezultatul ștergerii.",
|
||||
"priority": 27,
|
||||
"acceptanceCriteria": [
|
||||
"Toast arată rezultatul: 'X bonuri șterse'",
|
||||
"Bonurile șterse dispar instant din listă",
|
||||
"Selecția se golește după ștergere"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-028",
|
||||
"title": "Frontend - Navigare la Pagina Anterioară când Lista Devine Goală",
|
||||
"description": "Ca utilizator, vreau să fiu redirecționat când șterg toate bonurile de pe pagină.",
|
||||
"priority": 28,
|
||||
"acceptanceCriteria": [
|
||||
"După bulk delete, dacă lista devine goală și currentPage > 1, navigare la pagina anterioară",
|
||||
"Dacă eram pe pagina 1, afișează empty state"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-029",
|
||||
"title": "Frontend - Metodă updateReceiptInPlace în Store",
|
||||
"description": "Ca frontend, vreau să actualizez un singur rând fără să re-renderez toată lista.",
|
||||
"priority": 29,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda updateReceiptInPlace(receiptId, updates) în receiptsStore",
|
||||
"Object.assign pentru updates, nu înlocuire array"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-030",
|
||||
"title": "Backend - SSE Endpoint pentru Status Updates",
|
||||
"description": "Ca frontend, vreau să primesc notificări real-time despre schimbări de status.",
|
||||
"priority": 30,
|
||||
"acceptanceCriteria": [
|
||||
"GET /api/data-entry/receipts/sse/status returnează SSE stream",
|
||||
"Format eveniment: {receipt_id, status, processing_status}"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-031",
|
||||
"title": "Frontend - SSE Client Service",
|
||||
"description": "Ca frontend, vreau să mă conectez la SSE și să actualizez rândurile individual.",
|
||||
"priority": 31,
|
||||
"acceptanceCriteria": [
|
||||
"sseService.js cu connect(), disconnect(), onStatusChange()",
|
||||
"Folosește native EventSource API"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-032",
|
||||
"title": "Frontend - Înlocuire Polling cu SSE",
|
||||
"description": "Ca frontend, vreau să folosesc SSE în loc de polling.",
|
||||
"priority": 32,
|
||||
"acceptanceCriteria": [
|
||||
"SSE în loc de setInterval pentru auto-refresh",
|
||||
"La primire eveniment SSE, apelează updateReceiptInPlace()"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-033",
|
||||
"title": "Frontend - Graceful Degradation la SSE Failure",
|
||||
"description": "Ca utilizator, vreau ca aplicația să funcționeze și fără SSE.",
|
||||
"priority": 33,
|
||||
"acceptanceCriteria": [
|
||||
"Dacă SSE fail, activează fallback la polling clasic",
|
||||
"Retry SSE periodic (la 30s)"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed previously"
|
||||
},
|
||||
{
|
||||
"id": "US-034",
|
||||
"title": "Fix - Refresh Individual vs Refresh Total",
|
||||
"description": "Ca utilizator, vreau ca bonurile să se actualizeze individual fără să se reîncarce toată lista, pentru că vreau să văd progresul în timp real fără să pierd poziția.",
|
||||
"priority": 34,
|
||||
"acceptanceCriteria": [
|
||||
"SSE handler NU apelează store.fetchReceipts() când un receipt nu este în pagina curentă",
|
||||
"Verifică dacă receipt-ul aparține unui batch activ și îl adaugă local dacă nu există",
|
||||
"Ordinea bonurilor din batch rămâne stabilă (nu se reordonează)",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser: uploadează 5 bonuri, nu se reîncarcă pagina între procesări"
|
||||
],
|
||||
"technicalNotes": "Modificare în handleSSEStatusChange din ReceiptsListView.vue:2397. Când receipt nu e găsit: verifică dacă batch_id e în batchProgressStore, apoi fetch individual receipt și inserează local în poziția corectă.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 1"
|
||||
},
|
||||
{
|
||||
"id": "US-035",
|
||||
"title": "Fix - Bonuri cu Eroare Rămân în Listă",
|
||||
"description": "Ca utilizator, vreau ca bonurile cu eroare de procesare să rămână vizibile în listă, pentru că vreau să le pot edita manual sau să le șterg.",
|
||||
"priority": 35,
|
||||
"acceptanceCriteria": [
|
||||
"Bonurile cu processing_status='failed' NU sunt eliminate din view după refresh",
|
||||
"Eroarea de extragere dată/sumă afișează bonul cu status 'Eroare' și buton 'Editează'",
|
||||
"Link rapid 'Editează manual' duce la formularul de editare cu datele disponibile pre-populate",
|
||||
"Bonurile failed au highlight vizual (roșu subtil) pentru identificare ușoară",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser: bon cu eroare OCR rămâne în listă și poate fi editat"
|
||||
],
|
||||
"technicalNotes": "Bonurile cu eroare ar trebui să aibă status='draft' și processing_status='failed'. Nu se șterg la refresh, doar se actualizează in-place.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 2"
|
||||
},
|
||||
{
|
||||
"id": "US-036",
|
||||
"title": "Afișare Nume Fișier pentru Toate Bonurile",
|
||||
"description": "Ca utilizator, vreau să văd numele fișierului original pentru bonurile procesate, pentru că vreau să identific care bon corespunde cărui fișier uploadat.",
|
||||
"priority": 36,
|
||||
"acceptanceCriteria": [
|
||||
"Coloana 'Fișier' afișează original_filename și pentru receipt-uri completate, nu doar pentru job-uri",
|
||||
"Pe mobil, numele fișierului apare sub partener (font mic, gri)",
|
||||
"Tooltip pe desktop arată numele complet dacă e trunchiat",
|
||||
"Dacă receipt nu are original_filename, afișează '-'",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser: bon procesat afișează numele fișierului original"
|
||||
],
|
||||
"technicalNotes": "Backend trebuie să populeze original_filename pe receipt din job-ul OCR. Verifică că auto-save service copiază filename din job la receipt.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 3"
|
||||
},
|
||||
{
|
||||
"id": "US-037",
|
||||
"title": "Fix - Upload Nu Mai Face Refresh Automat",
|
||||
"description": "Ca utilizator, vreau ca selectarea fișierelor să nu reîncarce pagina, pentru că pierd fișierele selectate înainte să apăs 'Procesează'.",
|
||||
"priority": 37,
|
||||
"acceptanceCriteria": [
|
||||
"Selectarea fișierelor via input NU declanșează refresh/reload pagină",
|
||||
"Fișierele selectate rămân în listă până la submit explicit",
|
||||
"Comportament identic pe desktop și mobil (Chrome, Safari)",
|
||||
"Nu există race condition între clonarea fișierelor și resetarea input-ului",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: selectează 5 fișiere, toate rămân în listă"
|
||||
],
|
||||
"technicalNotes": "Bug probabil în onBulkFileInputChange sau în interacțiunea cu store. Verifică că nu există watchers care declanșează refresh la schimbări de state. Test specific pe Chrome Android - vezi gotcha din CLAUDE.md despre File API.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 4"
|
||||
},
|
||||
{
|
||||
"id": "US-038",
|
||||
"title": "Mobile - Selecție Multiplă prin Long-Press",
|
||||
"description": "Ca utilizator mobil, vreau să selectez bonuri prin apăsare lungă, pentru că vreau să pot șterge sau acționa asupra mai multor bonuri simultan.",
|
||||
"priority": 38,
|
||||
"acceptanceCriteria": [
|
||||
"Long-press (500ms) pe un card activează modul de selecție",
|
||||
"Card-ul selectat primește checkmark și background diferit (var(--blue-50))",
|
||||
"După activare, tap simplu pe alte carduri le adaugă/elimină din selecție",
|
||||
"Tap în afara cardurilor dezactivează modul selecție",
|
||||
"Header contextual arată numărul de elemente selectate",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: long-press selectează, tap adaugă la selecție"
|
||||
],
|
||||
"technicalNotes": "Implementare cu setTimeout(500ms) pe touchstart, clear pe touchend/touchmove. CSS: .receipt-card.selected { background: var(--blue-50); border-color: var(--blue-500); }",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 5"
|
||||
},
|
||||
{
|
||||
"id": "US-039",
|
||||
"title": "Mobile - Select All și Buton Ștergere",
|
||||
"description": "Ca utilizator mobil, vreau să am butoane 'Selectează tot' și 'Șterge' în modul selecție, pentru că vreau să pot șterge rapid mai multe bonuri.",
|
||||
"priority": 39,
|
||||
"acceptanceCriteria": [
|
||||
"În modul selecție, apare top bar contextual cu: număr selectate, buton 'Selectează tot', buton X pentru ieșire",
|
||||
"Apare bottom bar cu buton 'Șterge' (roșu, icon coș)",
|
||||
"'Selectează tot' selectează toate bonurile din pagina curentă",
|
||||
"Buton 'Șterge' afișează confirmare înainte de ștergere",
|
||||
"După ștergere, modul selecție se dezactivează",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: select all + delete funcționează"
|
||||
],
|
||||
"technicalNotes": "Top bar: position: sticky; top: 0. Bottom bar: position: fixed; bottom: 0. Animație slide-in pentru bars.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 6"
|
||||
},
|
||||
{
|
||||
"id": "US-040",
|
||||
"title": "Mobile - Layout Android Nativ pentru Lista Bonuri",
|
||||
"description": "Ca utilizator mobil, vreau o interfață similară cu aplicațiile Android native, pentru că vreau experiență familiară și intuitivă.",
|
||||
"priority": 40,
|
||||
"acceptanceCriteria": [
|
||||
"Top Bar fixă cu: hamburger/back (stânga), titlu centrat, search+filter+more (dreapta)",
|
||||
"Filter Chips sub top bar (orizontal scrollabil): Toate, Ciorne, În așteptare, Validate, Respinse",
|
||||
"Bottom Navigation fixă cu 4 tab-uri: Bonuri (activ), Upload, Rapoarte, Setări",
|
||||
"FAB (56x56px) în colț dreapta jos, 16px de la margine, 72px de la bottom",
|
||||
"FAB se ascunde când scroll în jos, apare când scroll în sus",
|
||||
"Lista ocupă spațiul dintre top bar și bottom nav",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: layout similar cu Gmail/WhatsApp"
|
||||
],
|
||||
"technicalNotes": "CSS: .mobile-top-bar { position: fixed; top: 0; height: 56px; z-index: 1000 }. .mobile-bottom-nav { position: fixed; bottom: 0; height: 56px }. .mobile-fab { position: fixed; bottom: 72px; right: 16px; width: 56px; border-radius: 16px }",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 7"
|
||||
},
|
||||
{
|
||||
"id": "US-041",
|
||||
"title": "Mobile - Layout Android pentru Editare/Creare Bon",
|
||||
"description": "Ca utilizator mobil, vreau interfață de editare bon similară cu compose email în Gmail, pentru că vreau butoane de acțiune accesibile și layout familiar.",
|
||||
"priority": 41,
|
||||
"acceptanceCriteria": [
|
||||
"Top Bar cu: X/back (stânga), titlu centrat, attach+save icons (dreapta)",
|
||||
"Content Area scrollabilă cu form fields 100% width",
|
||||
"Bottom Action Bar fixă cu: 'Salvează Ciornă' (secondary), 'Trimite pentru Validare' (primary)",
|
||||
"Keyboard-aware: bottom bar se mută deasupra tastaturii",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: layout similar cu Gmail compose"
|
||||
],
|
||||
"technicalNotes": "CSS: .mobile-receipt-form { padding-bottom: 80px }. .mobile-form-bottom-bar { position: fixed; bottom: 0; display: flex; gap: var(--space-sm) }. Butoane cu flex: 1 pentru width egal.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 8"
|
||||
},
|
||||
{
|
||||
"id": "US-042",
|
||||
"title": "Mobile - Layout Android pentru Vizualizare Bon",
|
||||
"description": "Ca utilizator mobil, vreau interfață de vizualizare bon similară cu view email în Gmail, pentru că vreau acțiuni rapide și navigare ușoară.",
|
||||
"priority": 42,
|
||||
"acceptanceCriteria": [
|
||||
"Top Bar cu: back arrow (stânga), edit+delete+more icons (dreapta)",
|
||||
"Content Area cu detalii bon (read-only)",
|
||||
"Bottom Action Bar cu butoane contextuale: DRAFT→Editează/Trimite, PENDING→Validează/Respinge, APPROVED→Anulează",
|
||||
"npm run typecheck passes",
|
||||
"Verify on mobile: acțiuni accesibile din bottom bar"
|
||||
],
|
||||
"technicalNotes": "Refolosește stilurile din US-041. Butoanele din bottom bar se schimbă dinamic bazat pe receipt.status.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 9"
|
||||
},
|
||||
{
|
||||
"id": "US-043",
|
||||
"title": "Păstrare Ordine Bonuri la Refresh",
|
||||
"description": "Ca utilizator, vreau ca bonurile să rămână în ordinea în care au fost uploadate, pentru că vreau să urmăresc progresul fiecărui bon uploadat.",
|
||||
"priority": 43,
|
||||
"acceptanceCriteria": [
|
||||
"Bonurile din același batch păstrează ordinea de upload (nu se reordonează)",
|
||||
"Un bon finalizat rămâne în aceeași poziție vizuală, nu sare la sfârșit",
|
||||
"SSE updates modifică status-ul in-place, fără a muta rândul",
|
||||
"Refresh manual poate reordona, dar SSE updates nu",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser: bon procesat nu își schimbă poziția în listă"
|
||||
],
|
||||
"technicalNotes": "Lista trebuie sortată by created_at DESC sau by batch order, nu by last_modified. updateReceiptInPlace din store NU trebuie să reordoneze array-ul.",
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 10"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
{
|
||||
"projectName": "unified-mobile-material-design",
|
||||
"branchName": "ralph/unified-mobile-md",
|
||||
"description": "Extinderea interfeței Material Design la toate paginile aplicației în modul mobil pentru consistență UI/UX",
|
||||
"cssRules": {
|
||||
"documentation": [
|
||||
"docs/ONBOARDING_CSS.md",
|
||||
"docs/DESIGN_TOKENS.md",
|
||||
"docs/CSS_PATTERNS.md",
|
||||
"docs/MOBILE_PATTERNS.md"
|
||||
],
|
||||
"goldenRules": [
|
||||
"Folosește DOAR design tokens - NICIODATĂ valori hardcodate",
|
||||
"Verifică CSS_PATTERNS.md înainte de a scrie CSS nou",
|
||||
"Testează în AMBELE teme (light + dark mode)",
|
||||
"NICIODATĂ :deep() în componente (PrimeVue → vendor/)",
|
||||
"NICIODATĂ duplicate CSS (write once, use everywhere)",
|
||||
"Mobile: toate paginile folosesc MobileTopBar + MobileBottomNav",
|
||||
"Mobile: filtrele se pun în BottomSheet, NU inline",
|
||||
"Mobile: selecția afișează acțiuni în footer, NU în header"
|
||||
],
|
||||
"mobileLayoutTokens": {
|
||||
"topBarHeight": "56px",
|
||||
"bottomNavHeight": "56px",
|
||||
"fabSize": "56px",
|
||||
"fabBottomOffset": "72px",
|
||||
"touchTargetMin": "48px"
|
||||
}
|
||||
},
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-101a",
|
||||
"title": "Creare MobileTopBar.vue component",
|
||||
"description": "Ca developer vreau componentă MobileTopBar.vue extrasă din ReceiptsListView pentru reutilizare",
|
||||
"priority": 1,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/MobileTopBar.vue creată",
|
||||
"Props: title (string), showBack (boolean), showMenu (boolean), actions (array)",
|
||||
"Emit events: menu-click, back-click, action-click",
|
||||
"Stiluri CSS extrase din ReceiptsListView (.mobile-top-bar)",
|
||||
"Dark mode support cu [data-theme='dark']",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 1"
|
||||
},
|
||||
{
|
||||
"id": "US-101b",
|
||||
"title": "Creare MobileBottomNav.vue component",
|
||||
"description": "Ca developer vreau componentă MobileBottomNav.vue pentru navigarea de jos pe mobil",
|
||||
"priority": 2,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/MobileBottomNav.vue creată",
|
||||
"Props: items (array of {to, icon, label, active})",
|
||||
"4 linkuri: Bonuri, Upload, Rapoarte, Setări",
|
||||
"Stiluri CSS extrase din ReceiptsListView (.mobile-bottom-nav)",
|
||||
"router-link pentru navigare",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 2"
|
||||
},
|
||||
{
|
||||
"id": "US-101c",
|
||||
"title": "Creare MobileSelectionFooter.vue component",
|
||||
"description": "Ca developer vreau componentă pentru acțiuni batch când sunt selectate elemente",
|
||||
"priority": 3,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/MobileSelectionFooter.vue creată",
|
||||
"Props: visible (boolean), actions (array of {label, icon, severity, handler})",
|
||||
"Animație slide-up cu Transition",
|
||||
"Stiluri din .mobile-selection-bottom-bar",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 3"
|
||||
},
|
||||
{
|
||||
"id": "US-102",
|
||||
"title": "Definire MD3 color tokens în CSS",
|
||||
"description": "Ca developer vreau variabile CSS pentru Material Design 3 color system",
|
||||
"priority": 4,
|
||||
"acceptanceCriteria": [
|
||||
"Fișier src/assets/css/core/md3-tokens.css creat",
|
||||
"Tokens: --md-sys-color-primary, --md-sys-color-on-primary, --md-sys-color-surface, --md-sys-color-on-surface, --md-sys-color-outline",
|
||||
"Dark mode variants cu [data-theme='dark']",
|
||||
"Auto dark mode cu @media (prefers-color-scheme: dark)",
|
||||
"Import adăugat în main.css",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 4"
|
||||
},
|
||||
{
|
||||
"id": "US-103",
|
||||
"title": "Refactor ReceiptsListView să folosească componente comune",
|
||||
"description": "Ca developer vreau ReceiptsListView să folosească noile componente pentru validare API",
|
||||
"priority": 5,
|
||||
"acceptanceCriteria": [
|
||||
"Import MobileTopBar, MobileBottomNav, MobileSelectionFooter în ReceiptsListView",
|
||||
"Înlocuire template HTML cu componente",
|
||||
"Ștergere CSS duplicat din ReceiptsListView (>200 linii)",
|
||||
"Funcționalitate identică cu implementarea actuală",
|
||||
"Verify in browser că lista bonuri funcționează identic pe mobil",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 5"
|
||||
},
|
||||
{
|
||||
"id": "US-104",
|
||||
"title": "Eliminare buton delete duplicat din header tabel",
|
||||
"description": "Ca utilizator vreau butonul delete doar în footer, nu și în header când e selecție activă",
|
||||
"priority": 6,
|
||||
"acceptanceCriteria": [
|
||||
"În ReceiptsListView, bulk-actions-bar afișat DOAR când !isMobile",
|
||||
"Pe mobil, delete apare DOAR în mobile-selection-bottom-bar (footer)",
|
||||
"Pe desktop, delete rămâne în bulk-actions-bar (header tabel)",
|
||||
"Verify in browser: pe mobil delete e doar în footer",
|
||||
"Verify in browser: pe desktop delete e în header",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 7"
|
||||
},
|
||||
{
|
||||
"id": "US-105",
|
||||
"title": "Adăugare buton Înapoi în editare bon",
|
||||
"description": "Ca utilizator vreau să pot reveni la lista de bonuri din pagina de editare cu buton ←",
|
||||
"priority": 7,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat în ReceiptCreateUnifiedView.vue",
|
||||
"showBack=true pentru a afișa săgeata ←",
|
||||
"Click pe ← navighează la /data-entry",
|
||||
"Title dinamic: 'Bon nou' pentru create, 'Editare Bon' pentru edit",
|
||||
"Verify in browser că butonul funcționează",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 8"
|
||||
},
|
||||
{
|
||||
"id": "US-112",
|
||||
"title": "Creare BottomSheet.vue component pentru filtre",
|
||||
"description": "Ca utilizator vreau să pot accesa filtrele într-un bottom sheet pe mobil",
|
||||
"priority": 8,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/BottomSheet.vue creată",
|
||||
"v-model:visible pentru control",
|
||||
"Animație slide-up smooth",
|
||||
"Drag handle în partea de sus",
|
||||
"Close pe tap outside overlay",
|
||||
"Slot default pentru conținut",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 9"
|
||||
},
|
||||
{
|
||||
"id": "US-106",
|
||||
"title": "Dashboard Mobile cu Swipeable KPI Cards",
|
||||
"description": "Ca utilizator vreau KPI-uri ca carousel swipeable pe mobil cu dots indicator",
|
||||
"priority": 9,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/SwipeableCards.vue creată",
|
||||
"Touch swipe left/right funcțional (touchstart, touchmove, touchend)",
|
||||
"Dots indicator pentru poziție curentă",
|
||||
"DashboardView.vue folosește componenta când isMobile",
|
||||
"Fallback la layout grid normal pe desktop",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 10"
|
||||
},
|
||||
{
|
||||
"id": "US-107",
|
||||
"title": "InvoicesView Mobile Material Design",
|
||||
"description": "Ca utilizator vreau pagina de facturi să aibă același header Material ca lista bonuri",
|
||||
"priority": 10,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat cu title 'Facturi'",
|
||||
"Actions în header: refresh (pi-refresh), export (pi-download)",
|
||||
"MobileBottomNav adăugat",
|
||||
"Filtre existente mutate în BottomSheet pe mobil",
|
||||
"Verify in browser că arată consistent cu bonuri",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 11"
|
||||
},
|
||||
{
|
||||
"id": "US-108",
|
||||
"title": "TrialBalanceView Mobile Material Design",
|
||||
"description": "Ca utilizator vreau pagina de balanță să aibă același header Material",
|
||||
"priority": 11,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat cu title 'Balanță de Verificare'",
|
||||
"Actions în header: export (pi-download)",
|
||||
"MobileBottomNav adăugat",
|
||||
"Filtre existente mutate în BottomSheet pe mobil",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 12"
|
||||
},
|
||||
{
|
||||
"id": "US-109",
|
||||
"title": "BankCashRegisterView Mobile Material Design",
|
||||
"description": "Ca utilizator vreau pagina de trezorerie să aibă același header Material",
|
||||
"priority": 12,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat cu title 'Trezorerie'",
|
||||
"Actions în header: refresh, export",
|
||||
"MobileBottomNav adăugat",
|
||||
"Filtre în BottomSheet pe mobil",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 13"
|
||||
},
|
||||
{
|
||||
"id": "US-110",
|
||||
"title": "ServerLogsView Mobile Material Design",
|
||||
"description": "Ca admin vreau pagina de loguri să aibă interfață Material",
|
||||
"priority": 13,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat cu title 'Loguri Server'",
|
||||
"Actions în header: refresh, export",
|
||||
"MobileBottomNav adăugat",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 14"
|
||||
},
|
||||
{
|
||||
"id": "US-111",
|
||||
"title": "CacheStatsView Mobile Material Design",
|
||||
"description": "Ca admin vreau pagina de statistici cache să aibă interfață Material",
|
||||
"priority": 14,
|
||||
"acceptanceCriteria": [
|
||||
"MobileTopBar adăugat cu title 'Statistici Cache'",
|
||||
"Actions în header: refresh",
|
||||
"MobileBottomNav adăugat",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 1"
|
||||
},
|
||||
{
|
||||
"id": "US-113",
|
||||
"title": "Batch Actions per Module contextuale",
|
||||
"description": "Ca utilizator vreau acțiuni batch diferite în funcție de modulul curent",
|
||||
"priority": 15,
|
||||
"acceptanceCriteria": [
|
||||
"MobileSelectionFooter acceptă prop actions ca array",
|
||||
"Bonuri: Delete + Export",
|
||||
"Facturi (dacă se adaugă selecție): Export + Print",
|
||||
"Fiecare acțiune are icon, label, severity, handler",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 2"
|
||||
},
|
||||
{
|
||||
"id": "US-118",
|
||||
"title": "Creare MOBILE_PATTERNS.md documentație",
|
||||
"description": "Ca developer vreau documentație dedicată pentru pattern-urile mobile",
|
||||
"priority": 16,
|
||||
"acceptanceCriteria": [
|
||||
"Fișier docs/MOBILE_PATTERNS.md creat",
|
||||
"Table of Contents cu linkuri rapide",
|
||||
"Secțiuni: MobileTopBar, MobileBottomNav, MobileSelectionFooter, BottomSheet, SwipeableCards",
|
||||
"Exemple de cod copy-paste pentru fiecare componentă",
|
||||
"Secțiune Quick Start pentru devs noi",
|
||||
"Diagrame ASCII pentru layout mobile"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 3"
|
||||
},
|
||||
{
|
||||
"id": "US-115",
|
||||
"title": "Actualizare CSS_PATTERNS.md cu Mobile Material Design",
|
||||
"description": "Ca developer viitor vreau referință la MOBILE_PATTERNS.md în CSS_PATTERNS.md",
|
||||
"priority": 17,
|
||||
"acceptanceCriteria": [
|
||||
"Secțiune nouă 'Mobile Material Design' în CSS_PATTERNS.md",
|
||||
"Link către docs/MOBILE_PATTERNS.md pentru detalii",
|
||||
"Rezumat scurt al componentelor disponibile",
|
||||
"Breakpoint-uri mobile documentate"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 4"
|
||||
},
|
||||
{
|
||||
"id": "US-116",
|
||||
"title": "Actualizare DESIGN_TOKENS.md cu MD3 Tokens",
|
||||
"description": "Ca developer vreau documentație pentru noile MD3 color tokens",
|
||||
"priority": 18,
|
||||
"acceptanceCriteria": [
|
||||
"Secțiune nouă 'Material Design 3 Tokens' în DESIGN_TOKENS.md",
|
||||
"Tabel cu toate variabilele --md-sys-color-*",
|
||||
"Mapping la tokens existenți",
|
||||
"Exemple pentru dark mode"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 5"
|
||||
},
|
||||
{
|
||||
"id": "US-117",
|
||||
"title": "Actualizare CLAUDE.md cu reguli Mobile Development",
|
||||
"description": "Ca Claude Code vreau reguli clare pentru dezvoltare mobilă",
|
||||
"priority": 19,
|
||||
"acceptanceCriteria": [
|
||||
"Secțiune nouă '### Mobile Development Rules' în CLAUDE.md",
|
||||
"Regulă: Toate paginile mobile folosesc MobileTopBar",
|
||||
"Regulă: Filtrele pe mobil în BottomSheet",
|
||||
"Regulă: Selecția afișează acțiuni în footer",
|
||||
"Regulă: Touch targets minim 44x44px",
|
||||
"Checklist rapid pentru code review mobile"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 6"
|
||||
},
|
||||
{
|
||||
"id": "US-119",
|
||||
"title": "Actualizare claude-learn-frontend.md cu pattern-uri noi",
|
||||
"description": "Ca Claude Code vreau pattern-urile salvate în memoria pentru sesiuni viitoare",
|
||||
"priority": 20,
|
||||
"acceptanceCriteria": [
|
||||
"Pattern: Mobile Material Design Component Architecture",
|
||||
"Pattern: Bottom Sheet Filter Pattern",
|
||||
"Pattern: Mobile Selection Mode Flow",
|
||||
"Gotcha: Nu duplica delete button în header și footer",
|
||||
"Format corect cu @date și tags #mobile #material-design"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 7"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
{
|
||||
"projectName": "mobile-navigation-improvements",
|
||||
"branchName": "ralph/unified-mobile-md",
|
||||
"description": "Extinderea navigării mobile cu Dashboard split, Settings Hub, MobileDrawerMenu și butoane context-aware",
|
||||
"cssRules": {
|
||||
"documentation": [
|
||||
"docs/ONBOARDING_CSS.md",
|
||||
"docs/DESIGN_TOKENS.md",
|
||||
"docs/CSS_PATTERNS.md",
|
||||
"docs/MOBILE_PATTERNS.md"
|
||||
],
|
||||
"goldenRules": [
|
||||
"Folosește DOAR design tokens - NICIODATĂ valori hardcodate",
|
||||
"Verifică CSS_PATTERNS.md înainte de a scrie CSS nou",
|
||||
"Testează în AMBELE teme (light + dark mode)",
|
||||
"NICIODATĂ :deep() în componente (PrimeVue → vendor/)",
|
||||
"NICIODATĂ duplicate CSS (write once, use everywhere)",
|
||||
"Mobile: toate paginile folosesc MobileTopBar + MobileBottomNav",
|
||||
"Mobile: filtrele se pun în BottomSheet, NU inline",
|
||||
"Mobile: selecția afișează acțiuni în footer, NU în header"
|
||||
],
|
||||
"mobileLayoutTokens": {
|
||||
"topBarHeight": "56px",
|
||||
"bottomNavHeight": "56px",
|
||||
"fabSize": "56px",
|
||||
"fabBottomOffset": "72px",
|
||||
"touchTargetMin": "48px"
|
||||
}
|
||||
},
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-201",
|
||||
"title": "MobileBottomNav activ pe Dashboard",
|
||||
"description": "Ca utilizator mobil vreau să văd footer navigation și pe Dashboard pentru navigare ușoară",
|
||||
"priority": 1,
|
||||
"acceptanceCriteria": [
|
||||
"DashboardView.vue include MobileBottomNav component",
|
||||
"Link-ul 'Rapoarte' din nav este activ/highlighted pe Dashboard",
|
||||
"MobileTopBar adăugat cu title 'Dashboard'",
|
||||
"Verify in browser: navigarea funcționează din Dashboard",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 1"
|
||||
},
|
||||
{
|
||||
"id": "US-202",
|
||||
"title": "MobileDrawerMenu Material Design cu Profil",
|
||||
"description": "Ca utilizator mobil vreau meniul hamburger să aibă design Material cu secțiune profil",
|
||||
"priority": 2,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/MobileDrawerMenu.vue creată",
|
||||
"Secțiuni: Header cu logo, Navigare principală, Profil utilizator (nume + logout)",
|
||||
"Link-uri navigare: Dashboard, Bonuri, Facturi, Balanță, Trezorerie, Setări",
|
||||
"Active state pe link-ul curent (bazat pe route)",
|
||||
"Animație slide-in de la stânga",
|
||||
"Close pe tap outside sau pe link click",
|
||||
"Dark mode support",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 2"
|
||||
},
|
||||
{
|
||||
"id": "US-208",
|
||||
"title": "Actualizare Router cu noile rute",
|
||||
"description": "Ca developer vreau router-ul configurat pentru noile pagini",
|
||||
"priority": 3,
|
||||
"acceptanceCriteria": [
|
||||
"Rută /reports/maturity-analysis → MaturityAnalysisView (lazy loaded)",
|
||||
"Rută /reports/detailed-invoices → DetailedInvoicesView (lazy loaded)",
|
||||
"Rută /settings → SettingsHubView (lazy loaded)",
|
||||
"Redirect /data-entry/ocr-metrics accesibil din Settings Hub",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 3"
|
||||
},
|
||||
{
|
||||
"id": "US-206",
|
||||
"title": "Creare Pagină Hub Setări",
|
||||
"description": "Ca utilizator vreau o pagină centrală cu toate opțiunile de setări",
|
||||
"priority": 4,
|
||||
"acceptanceCriteria": [
|
||||
"View nou: src/modules/reports/views/SettingsHubView.vue",
|
||||
"Route: /settings",
|
||||
"Carduri pentru: OCR Setări, Cache Stats, Loguri Server, Telegram (dacă admin)",
|
||||
"MobileTopBar cu title 'Setări'",
|
||||
"MobileBottomNav cu Setări activ",
|
||||
"Click pe card navighează la pagina respectivă",
|
||||
"Design responsive (grid 2x2 pe mobil, 4 columns pe desktop)",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 4"
|
||||
},
|
||||
{
|
||||
"id": "US-204",
|
||||
"title": "Creare Pagină Analiză Scadențe",
|
||||
"description": "Ca utilizator vreau o pagină dedicată pentru analiza scadențelor clienți/furnizori",
|
||||
"priority": 5,
|
||||
"acceptanceCriteria": [
|
||||
"View nou: src/modules/reports/views/MaturityAnalysisView.vue",
|
||||
"Route: /reports/maturity-analysis",
|
||||
"Conține componenta MaturityAndDetailsCard (partea de analiză scadențe)",
|
||||
"MobileTopBar cu title 'Analiză Scadențe' și buton ← Înapoi la Dashboard",
|
||||
"MobileBottomNav activ",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 5"
|
||||
},
|
||||
{
|
||||
"id": "US-205",
|
||||
"title": "Creare Pagină Facturi Detaliate",
|
||||
"description": "Ca utilizator vreau o pagină dedicată pentru tabelul facturilor detaliate",
|
||||
"priority": 6,
|
||||
"acceptanceCriteria": [
|
||||
"View nou: src/modules/reports/views/DetailedInvoicesView.vue",
|
||||
"Route: /reports/detailed-invoices",
|
||||
"Conține componenta/partea de tabel detaliat din MaturityAndDetailsCard",
|
||||
"MobileTopBar cu title 'Facturi Detaliate' și buton ← Înapoi",
|
||||
"MobileBottomNav activ",
|
||||
"Filtre în BottomSheet pe mobil",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 6"
|
||||
},
|
||||
{
|
||||
"id": "US-203",
|
||||
"title": "Dashboard KPIs Only View",
|
||||
"description": "Ca utilizator mobil vreau Dashboard-ul principal să arate doar KPI cards pentru acces rapid",
|
||||
"priority": 7,
|
||||
"acceptanceCriteria": [
|
||||
"DashboardView.vue pe mobil afișează DOAR: SwipeableCards cu KPIs",
|
||||
"Adăugare 2 carduri quick-link: 'Analiză Scadențe →' și 'Facturi Detaliate →'",
|
||||
"Click pe quick-link navighează la pagina dedicată",
|
||||
"Desktop rămâne neschimbat (toate secțiunile)",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 7"
|
||||
},
|
||||
{
|
||||
"id": "US-212",
|
||||
"title": "Quick Links în Dashboard Mobile",
|
||||
"description": "Ca utilizator vreau carduri clickabile în Dashboard pentru a ajunge la Scadențe și Facturi",
|
||||
"priority": 8,
|
||||
"acceptanceCriteria": [
|
||||
"După SwipeableCards, 2 carduri: 'Analiză Scadențe' și 'Facturi Detaliate'",
|
||||
"Design: icon + titlu + săgeată dreaptă (→)",
|
||||
"Click navighează la pagina respectivă",
|
||||
"Doar pe mobil (desktop are totul în aceeași pagină)",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 8"
|
||||
},
|
||||
{
|
||||
"id": "US-207",
|
||||
"title": "Actualizare MobileBottomNav cu link Setări",
|
||||
"description": "Ca utilizator vreau butonul Setări din footer să ducă la Hub Setări",
|
||||
"priority": 9,
|
||||
"acceptanceCriteria": [
|
||||
"MobileBottomNav: link Setări → /settings (nu /data-entry/ocr-metrics)",
|
||||
"Icon: pi-cog",
|
||||
"Active state când pe /settings sau /settings/* sau pagini admin",
|
||||
"Actualizare în toate paginile care folosesc MobileBottomNav",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 9"
|
||||
},
|
||||
{
|
||||
"id": "US-213",
|
||||
"title": "Actualizare Hamburger Menu în toate paginile",
|
||||
"description": "Ca utilizator vreau hamburger menu-ul să funcționeze uniform în toată aplicația",
|
||||
"priority": 10,
|
||||
"acceptanceCriteria": [
|
||||
"Toate paginile cu MobileTopBar: click pe ☰ deschide MobileDrawerMenu",
|
||||
"MobileDrawerMenu înlocuiește vechiul Sidebar pe mobil",
|
||||
"Desktop păstrează navigarea existentă",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 10"
|
||||
},
|
||||
{
|
||||
"id": "US-210",
|
||||
"title": "Creare MobileActionBar Component",
|
||||
"description": "Ca developer vreau componentă reutilizabilă pentru butoane de acțiune pe mobil",
|
||||
"priority": 11,
|
||||
"acceptanceCriteria": [
|
||||
"Componentă src/shared/components/mobile/MobileActionBar.vue creată",
|
||||
"Props: actions (array of {label, icon, severity, handler, disabled})",
|
||||
"Layout: butoane full-width sau side-by-side (2 butoane)",
|
||||
"Position: fixed bottom, above MobileBottomNav",
|
||||
"Animație: slide-up la mount",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 11"
|
||||
},
|
||||
{
|
||||
"id": "US-209",
|
||||
"title": "Butoane Context-Aware în Editare Bon",
|
||||
"description": "Ca utilizator vreau butoanele din editare bon să se schimbe în funcție de starea bonului",
|
||||
"priority": 12,
|
||||
"acceptanceCriteria": [
|
||||
"ReceiptCreateUnifiedView detectează starea bonului (draft/pending/approved/rejected)",
|
||||
"Draft: Salvează | Submit pentru Aprobare | Șterge",
|
||||
"Pending: Salvează | Aprobă | Respinge (dacă are permisiuni)",
|
||||
"Approved: doar vizualizare (butoane disabled sau ascunse)",
|
||||
"Rejected: Salvează (re-edit) | Re-submit",
|
||||
"Butoane în MobileActionBar fix jos pe mobil",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 12"
|
||||
},
|
||||
{
|
||||
"id": "US-211",
|
||||
"title": "Integrare MobileActionBar în ReceiptCreateUnifiedView",
|
||||
"description": "Ca utilizator vreau butoanele de acțiune pentru bon să fie în action bar pe mobil",
|
||||
"priority": 13,
|
||||
"acceptanceCriteria": [
|
||||
"ReceiptCreateUnifiedView folosește MobileActionBar pe mobil",
|
||||
"Desktop păstrează butoanele în form",
|
||||
"Buton Înapoi în MobileTopBar (deja există din US-105)",
|
||||
"Action bar dispare când se deschide BottomSheet sau alte overlay-uri",
|
||||
"npm run build passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 13"
|
||||
},
|
||||
{
|
||||
"id": "US-214",
|
||||
"title": "Actualizare Documentație MOBILE_PATTERNS.md",
|
||||
"description": "Ca developer viitor vreau documentația actualizată cu noile componente și pattern-uri",
|
||||
"priority": 14,
|
||||
"acceptanceCriteria": [
|
||||
"Secțiune nouă: MobileDrawerMenu (usage, props)",
|
||||
"Secțiune nouă: MobileActionBar (usage, props)",
|
||||
"Secțiune actualizată: Navigation patterns (footer → settings hub)",
|
||||
"Diagrame ASCII actualizate cu noile rute",
|
||||
"npm run build passes (doar pentru validare că nu s-a stricat nimic)"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 14"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
{
|
||||
"projectName": "financial-indicators-dashboard",
|
||||
"branchName": "ralph/financial-indicators-dashboard",
|
||||
"description": "Adaugare card indicatori financiari in dashboard solduri cu rate de lichiditate, eficienta, risc si Altman Z-Score pentru evaluare bancara/creditare",
|
||||
"cssRules": {
|
||||
"documentation": [
|
||||
"docs/ONBOARDING_CSS.md",
|
||||
"docs/DESIGN_TOKENS.md",
|
||||
"docs/CSS_PATTERNS.md",
|
||||
"docs/MOBILE_PATTERNS.md"
|
||||
],
|
||||
"goldenRules": [
|
||||
"Foloseste DOAR design tokens - NICIODATA valori hardcodate",
|
||||
"Testeaza in AMBELE teme (light + dark mode)",
|
||||
"Mobile: touch targets minim 44x44px",
|
||||
"Backend: foloseste decorator @cached pentru caching",
|
||||
"Backend: urmeaza pattern-ul din dashboard_service.py"
|
||||
]
|
||||
},
|
||||
"referenceFiles": {
|
||||
"dashboardService": "backend/modules/reports/services/dashboard_service.py",
|
||||
"dashboardRouter": "backend/modules/reports/routers/dashboard.py",
|
||||
"dashboardView": "src/modules/reports/views/DashboardView.vue",
|
||||
"dashboardStore": "src/modules/reports/stores/dashboard.js",
|
||||
"existingCards": [
|
||||
"src/modules/reports/components/dashboard/cards/TreasuryDualCard.vue",
|
||||
"src/modules/reports/components/dashboard/cards/CashFlowMetricCard.vue",
|
||||
"src/modules/reports/components/solduri/SolduriCompactCard.vue"
|
||||
]
|
||||
},
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Backend - Serviciu Agregare Conturi Balanta",
|
||||
"description": "Ca dezvoltator backend, vreau un serviciu care agregeaza soldurile din balanta de verificare (VBAL) pe clase de conturi, pentru ca am nevoie de date agregate pentru calculul indicatorilor de bilant si Altman Z-Score",
|
||||
"priority": 1,
|
||||
"acceptanceCriteria": [
|
||||
"Creat backend/modules/reports/services/financial_indicators_service.py",
|
||||
"Clasa FinancialIndicatorsService cu metodele statice necesare",
|
||||
"Constanta ACCOUNT_GROUPS cu prefixele conturilor pentru fiecare categorie",
|
||||
"Metoda get_balance_sheet_aggregates(company_id, luna, an) returneaza dict cu solduri agregate pentru: active_imobilizate, stocuri, creante, disponibilitati, capital_propriu, rezultat, datorii_termen_lung, datorii_curente, venituri, cheltuieli_operationale",
|
||||
"Query-ul foloseste VBAL view cu LIKE pentru prefixe conturi (ex: cont LIKE '20%')",
|
||||
"Cache implementat cu decorator @cached(cache_type='financial_indicators', key_params=['company_id', 'luna', 'an'])",
|
||||
"Structura raspunsului documentata cu Pydantic model BalanceSheetAggregates"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 1"
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Backend - Calcul Indicatori Lichiditate",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad indicatorii de lichiditate calculati automat, pentru ca vreau sa stiu daca firma poate plati datoriile pe termen scurt",
|
||||
"priority": 2,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_liquidity_indicators(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat lichiditate_curenta = active_curente / datorii_curente",
|
||||
"Calculat lichiditate_imediata (Quick Ratio) = (disponibilitati + creante) / datorii_curente",
|
||||
"Calculat lichiditate_vedere (Cash Ratio) = disponibilitati / datorii_curente",
|
||||
"Fiecare indicator returneaza dict cu: value, status (good/warning/danger), threshold_min, threshold_max",
|
||||
"Status: good pentru lichiditate_imediata >= 1.0, warning pentru 0.5-1.0, danger pentru < 0.5",
|
||||
"Handle cazul cand datorii_curente = 0 (returneaza null sau infinit cu mesaj)"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 2"
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Backend - Calcul Indicatori Eficienta",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad indicatorii de eficienta (DSO, DPO, rate), pentru ca vreau sa stiu cat de repede convertesc resursele in bani",
|
||||
"priority": 3,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_efficiency_indicators(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat dso (Durata Incasare) = (clienti_sold / facturari_lunare) * 30",
|
||||
"Calculat dpo (Durata Plata) = (furnizori_sold / achizitii_lunare) * 30",
|
||||
"Calculat cash_conversion_cycle = dso - dpo",
|
||||
"Calculat rata_incasare = incasari / facturari * 100",
|
||||
"Calculat rata_plata = plati / achizitii * 100",
|
||||
"Status: good pentru DSO < 30, warning pentru 30-45, danger pentru > 45",
|
||||
"Foloseste datele din summary si trends existente pentru facturari/incasari"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 3"
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Backend - Calcul Indicatori Risc si Aging",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad indicatorii de risc si aging creante/datorii, pentru ca vreau sa stiu cat de sanatos este portofoliul de creante",
|
||||
"priority": 4,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_risk_indicators(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat creante_restante_pct = clienti_sold_restant / clienti_sold_total * 100",
|
||||
"Calculat creante_90plus_pct = clienti_restant_90plus / clienti_sold_total * 100",
|
||||
"Calculat datorii_restante_pct = furnizori_sold_restant / furnizori_sold_total * 100",
|
||||
"Calculat raport_datorii_trezorerie = furnizori_sold_total / trezorerie",
|
||||
"Status: good pentru creante_restante_pct < 20%, warning pentru 20-30%, danger pentru > 30%",
|
||||
"Foloseste datele din summary existente pentru aging"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 4"
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Backend - Calcul Indicatori Cash Flow",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad indicatorii de cash flow, pentru ca vreau sa stiu daca firma genereaza sau consuma numerar",
|
||||
"priority": 5,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_cashflow_indicators(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat flux_net_lunar = incasari_luna - plati_luna",
|
||||
"Calculat cash_flow_ytd = suma fluxurilor de la ianuarie pana la luna curenta",
|
||||
"Calculat flux_net_yoy_pct = (cf_curent - cf_anterior) / abs(cf_anterior) * 100",
|
||||
"Calculat acoperire_cash_flow = cash_flow_ytd / datorii_restante",
|
||||
"Status: good pentru flux_net > 0, danger pentru flux_net < 0",
|
||||
"Foloseste datele din trends existente pentru incasari/plati istorice"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 5"
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Backend - Calcul Indicatori Dinamica",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad evolutia vanzarilor si achizitiilor, pentru ca vreau sa stiu daca afacerea creste sau scade",
|
||||
"priority": 6,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_dynamics_indicators(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat crestere_vanzari_yoy = (facturari_curent - facturari_anterior) / facturari_anterior * 100",
|
||||
"Calculat crestere_achizitii_yoy = (achizitii_curent - achizitii_anterior) / achizitii_anterior * 100",
|
||||
"Calculat marja_implicita = (facturari - achizitii) / facturari * 100",
|
||||
"Status: good pentru crestere_vanzari > 5%, warning pentru 0-5%, danger pentru < 0%",
|
||||
"Foloseste datele din trends existente cu comparatie YoY"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 6"
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Backend - Calcul Altman Z-Score",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau sa vad scorul Altman Z-Score calculat automat, pentru ca vreau sa stiu riscul de faliment al firmei conform standardelor internationale",
|
||||
"priority": 7,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda calculate_altman_zscore(company_id, luna, an) in FinancialIndicatorsService",
|
||||
"Calculat working_capital = active_curente - datorii_curente",
|
||||
"Calculat total_assets = suma tuturor activelor din VBAL",
|
||||
"Calculat X1 = working_capital / total_assets",
|
||||
"Calculat X2 = rezultat_reportat (cont 117 + 121) / total_assets",
|
||||
"Calculat X3 = ebit (venituri - cheltuieli_operationale) / total_assets",
|
||||
"Calculat X4 = capital_propriu / (datorii_curente + datorii_termen_lung)",
|
||||
"Calculat zscore = 6.56*X1 + 3.26*X2 + 6.72*X3 + 1.05*X4",
|
||||
"Status: safe pentru zscore > 2.60, grey pentru 1.10-2.60, distress pentru < 1.10",
|
||||
"Raspunsul include componentele individuale x1, x2, x3, x4 pentru transparenta"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 7"
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Backend - Endpoint API Financial Indicators",
|
||||
"description": "Ca frontend developer, vreau un endpoint API care returneaza toti indicatorii calculati, pentru ca am nevoie sa afisez datele in UI",
|
||||
"priority": 8,
|
||||
"acceptanceCriteria": [
|
||||
"Endpoint GET /api/reports/dashboard/financial-indicators in dashboard.py router",
|
||||
"Parametri: company (required int), luna (optional int), an (optional int)",
|
||||
"Apeleaza toate metodele calculate_* din FinancialIndicatorsService",
|
||||
"Raspuns JSON cu structura: { lichiditate: {...}, eficienta: {...}, risc: {...}, cash_flow: {...}, dinamica: {...}, altman_zscore: {...} }",
|
||||
"Fiecare indicator include: value, status, threshold_min, threshold_max",
|
||||
"Cache 30 minute implementat cu decorator @cached",
|
||||
"Response model Pydantic FinancialIndicatorsResponse definit",
|
||||
"Endpoint documentat cu docstring si tags=['dashboard']"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 8"
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Backend - Date Istorice pentru Sparklines",
|
||||
"description": "Ca utilizator, vreau sa vad evolutia fiecarui indicator pe 12 luni, pentru ca vreau sa inteleg trendul, nu doar valoarea curenta",
|
||||
"priority": 9,
|
||||
"acceptanceCriteria": [
|
||||
"Metoda get_historical_indicators(company_id, months=12) in FinancialIndicatorsService",
|
||||
"Calculeaza indicatorii pentru fiecare din ultimele 12 luni",
|
||||
"Returneaza dict cu sparkline_data (array 12 valori) si sparkline_labels (array 12 etichete)",
|
||||
"Etichete in format 'MMM YY' (ex: 'Feb 24', 'Mar 24')",
|
||||
"Integreaza sparkline_data in raspunsul fiecarui indicator",
|
||||
"Cache separat pentru date istorice (TTL 1 ora) pentru performanta"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 9"
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Frontend - Component FinancialIndicatorsCard",
|
||||
"description": "Ca utilizator al dashboard-ului, vreau un card vizual care afiseaza indicatorii financiari, pentru ca vreau sa vad rapid starea financiara a firmei",
|
||||
"priority": 10,
|
||||
"acceptanceCriteria": [
|
||||
"Creat src/modules/reports/components/dashboard/cards/FinancialIndicatorsCard.vue",
|
||||
"Header cu titlu 'Indicatori Financiari' si selector perioada (dropdown luna/an)",
|
||||
"TabView PrimeVue pentru categorii: Lichiditate, Eficienta, Risc, Z-Score",
|
||||
"Grid 2x2 pentru indicatori in fiecare tab",
|
||||
"Foloseste design tokens: var(--space-md), var(--surface-card), var(--text-color)",
|
||||
"Props: loading (boolean), error (string), data (object din store)",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser that card-ul se afiseaza corect pe http://localhost:3000"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 10"
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Frontend - Component IndicatorItem cu Sparkline",
|
||||
"description": "Ca utilizator, vreau fiecare indicator sa aiba o mini-diagrama de evolutie, pentru ca vreau sa vad trendul vizual",
|
||||
"priority": 11,
|
||||
"acceptanceCriteria": [
|
||||
"Creat src/modules/reports/components/dashboard/cards/IndicatorItem.vue",
|
||||
"Props: label (string), value (number), unit (string), status (good/warning/danger), sparklineData (array), thresholds (object)",
|
||||
"Afiseaza: label sus, valoare mare centrata, sparkline jos, status icon dreapta",
|
||||
"Cod culoare: verde var(--green-600), galben var(--yellow-600), rosu var(--red-600)",
|
||||
"Sparkline implementat cu SVG polyline, responsive la container width",
|
||||
"Tooltip la hover pe sparkline arata valoarea lunii",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser that sparkline-urile se afiseaza corect"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 11"
|
||||
},
|
||||
{
|
||||
"id": "US-012",
|
||||
"title": "Frontend - Expand pentru Detalii Complete",
|
||||
"description": "Ca utilizator, vreau sa pot expanda cardul pentru a vedea toti indicatorii, pentru ca unii indicatori sunt mai putin importanti dar vreau acces la ei",
|
||||
"priority": 12,
|
||||
"acceptanceCriteria": [
|
||||
"Buton chevron in footer-ul cardului pentru expand/collapse",
|
||||
"State ref expanded = false, toggle la click",
|
||||
"Starea collapsed arata 4 indicatori principali: Quick Ratio, DSO, Creante Restante %, Z-Score",
|
||||
"Starea expanded arata toti indicatorii in DataTable PrimeVue cu coloane: Indicator, Valoare, Status, Trend",
|
||||
"Animatie CSS transition pe max-height pentru smooth expand",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser that expand/collapse functioneaza fluid"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 12"
|
||||
},
|
||||
{
|
||||
"id": "US-013",
|
||||
"title": "Frontend - Store Integration",
|
||||
"description": "Ca frontend developer, vreau sa integrez datele in Pinia store, pentru ca am nevoie de state management pentru indicatori",
|
||||
"priority": 13,
|
||||
"acceptanceCriteria": [
|
||||
"In src/modules/reports/stores/dashboard.js adaugat state: financialIndicators: { loading: false, error: null, data: null }",
|
||||
"Metoda async loadFinancialIndicators(companyId, luna, an) care face GET /api/reports/dashboard/financial-indicators",
|
||||
"Seteaza loading=true la inceput, loading=false si data/error la final",
|
||||
"Computed getters: lichiditate, eficienta, risc, cashFlow, dinamica, altmanZScore care extrag din data",
|
||||
"Error handling cu try/catch si mesaj user-friendly",
|
||||
"npm run typecheck passes"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 13"
|
||||
},
|
||||
{
|
||||
"id": "US-014",
|
||||
"title": "Frontend - Integrare in DashboardView Desktop",
|
||||
"description": "Ca utilizator pe desktop, vreau sa vad cardul de indicatori in dashboard, pentru ca vreau acces rapid la informatii",
|
||||
"priority": 14,
|
||||
"acceptanceCriteria": [
|
||||
"Import FinancialIndicatorsCard in DashboardView.vue",
|
||||
"Adaugat in template pe desktop (v-if=!isMobile) dupa sectiunea metrics-cards-section",
|
||||
"Div wrapper cu clasa financial-indicators-section, margin-top: var(--space-lg)",
|
||||
"Card ocupa full width",
|
||||
"In loadDashboardData() adaugat apel dashboardStore.loadFinancialIndicators(companyId, luna, an)",
|
||||
"Paseaza props: :loading :error :data din store",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser (desktop 1200px+) that cardul apare corect sub grafice"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 14"
|
||||
},
|
||||
{
|
||||
"id": "US-015",
|
||||
"title": "Frontend - Integrare in DashboardView Mobile",
|
||||
"description": "Ca utilizator pe mobil, vreau sa vad cardul de indicatori in carusel, pentru ca vreau acces la informatii si pe telefon",
|
||||
"priority": 15,
|
||||
"acceptanceCriteria": [
|
||||
"In SwipeableCards din DashboardView adaugat pagina 6 (index 5)",
|
||||
"Totalul de pagini devine 6 (era 5)",
|
||||
"Pagina 6 contine FinancialIndicatorsCard adaptat pentru mobil",
|
||||
"Layout single column pe mobil, tabs mai mici",
|
||||
"Respecta padding: padding-top 0 (e in SwipeableCards), padding-bottom 0",
|
||||
"Page indicator dots actualizeaza la 6 puncte",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser (mobile 375px) that swipe la pagina 6 arata cardul indicatori"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 15"
|
||||
},
|
||||
{
|
||||
"id": "US-016",
|
||||
"title": "Frontend - Dark Mode Support",
|
||||
"description": "Ca utilizator, vreau cardul sa arate bine in dark mode, pentru ca folosesc aplicatia si seara",
|
||||
"priority": 16,
|
||||
"acceptanceCriteria": [
|
||||
"Culorile status (verde/galben/rosu) vizibile pe fundal inchis - foloseste var(--green-400), var(--yellow-400), var(--red-400) in dark mode",
|
||||
"Sparkline stroke color foloseste var(--primary-color) care se adapteaza la tema",
|
||||
"Background card foloseste var(--surface-card) care se adapteaza",
|
||||
"Text foloseste var(--text-color) care se adapteaza",
|
||||
"NU sunt valori de culoare hardcodate (#fff, #000, etc)",
|
||||
"npm run typecheck passes",
|
||||
"Verify in browser cu toggle dark mode that totul e lizibil si contrastant"
|
||||
],
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 16"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
# Ralph Progress Log
|
||||
Started: 2026-01-19
|
||||
Project: financial-indicators-dashboard
|
||||
Branch: ralph/financial-indicators-dashboard
|
||||
---
|
||||
|
||||
## Obiectiv
|
||||
Adaugare card indicatori financiari in dashboard solduri cu:
|
||||
- Rate de lichiditate (Quick Ratio, Cash Ratio, Current Ratio)
|
||||
- Indicatori eficienta (DSO, DPO, Cash Conversion Cycle)
|
||||
- Indicatori risc (% Creante Restante, Aging 90+)
|
||||
- Cash Flow indicators
|
||||
- Altman Z-Score pentru evaluare risc faliment
|
||||
|
||||
## User Stories Status (16 total)
|
||||
|
||||
### Backend (US-001 to US-009)
|
||||
- US-001: Serviciu Agregare Conturi Balanta - PENDING
|
||||
- US-002: Calcul Indicatori Lichiditate - PENDING
|
||||
- US-003: Calcul Indicatori Eficienta - PENDING
|
||||
- US-004: Calcul Indicatori Risc si Aging - PENDING
|
||||
- US-005: Calcul Indicatori Cash Flow - PENDING
|
||||
- US-006: Calcul Indicatori Dinamica - PENDING
|
||||
- US-007: Calcul Altman Z-Score - PENDING
|
||||
- US-008: Endpoint API Financial Indicators - PENDING
|
||||
- US-009: Date Istorice pentru Sparklines - PENDING
|
||||
|
||||
### Frontend (US-010 to US-016)
|
||||
- US-010: Component FinancialIndicatorsCard - PENDING
|
||||
- US-011: Component IndicatorItem cu Sparkline - PENDING
|
||||
- US-012: Expand pentru Detalii Complete - PENDING
|
||||
- US-013: Store Integration - PENDING
|
||||
- US-014: Integrare in DashboardView Desktop - PENDING
|
||||
- US-015: Integrare in DashboardView Mobile - PENDING
|
||||
- US-016: Dark Mode Support - PENDING
|
||||
|
||||
---
|
||||
[2026-01-19 14:43:14] Starting Ralph for project: financial-indicators-dashboard
|
||||
[2026-01-19 14:43:14] Max iterations: 20
|
||||
[2026-01-19 14:43:14] Creating new branch: ralph/financial-indicators-dashboard
|
||||
[2026-01-19 14:43:14] === Iteration 1/20 ===
|
||||
[2026-01-19 14:43:14] Working on story: US-001
|
||||
[2026-01-19 14:43:14] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_1_US-001.log)
|
||||
[2026-01-19 14:47:40] SUCCESS: Story US-001 passed!
|
||||
[2026-01-19 14:47:40] Changes committed
|
||||
[2026-01-19 14:47:40] Progress: 1/16 stories completed
|
||||
[2026-01-19 14:47:42] === Iteration 2/20 ===
|
||||
[2026-01-19 14:47:42] Working on story: US-002
|
||||
[2026-01-19 14:47:42] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_2_US-002.log)
|
||||
[2026-01-19 14:49:51] SUCCESS: Story US-002 passed!
|
||||
[2026-01-19 14:49:51] Changes committed
|
||||
[2026-01-19 14:49:51] Progress: 2/16 stories completed
|
||||
[2026-01-19 14:49:53] === Iteration 3/20 ===
|
||||
[2026-01-19 14:49:53] Working on story: US-003
|
||||
[2026-01-19 14:49:53] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_3_US-003.log)
|
||||
[2026-01-19 14:52:59] SUCCESS: Story US-003 passed!
|
||||
[2026-01-19 14:52:59] Changes committed
|
||||
[2026-01-19 14:52:59] Progress: 3/16 stories completed
|
||||
[2026-01-19 14:53:01] === Iteration 4/20 ===
|
||||
[2026-01-19 14:53:01] Working on story: US-004
|
||||
[2026-01-19 14:53:01] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_4_US-004.log)
|
||||
[2026-01-19 14:56:40] SUCCESS: Story US-004 passed!
|
||||
[2026-01-19 14:56:41] Changes committed
|
||||
[2026-01-19 14:56:41] Progress: 4/16 stories completed
|
||||
[2026-01-19 14:56:43] === Iteration 5/20 ===
|
||||
[2026-01-19 14:56:43] Working on story: US-005
|
||||
[2026-01-19 14:56:43] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_5_US-005.log)
|
||||
[2026-01-19 15:00:30] SUCCESS: Story US-005 passed!
|
||||
[2026-01-19 15:00:30] Changes committed
|
||||
[2026-01-19 15:00:30] Progress: 5/16 stories completed
|
||||
[2026-01-19 15:00:32] === Iteration 6/20 ===
|
||||
[2026-01-19 15:00:32] Working on story: US-006
|
||||
[2026-01-19 15:00:32] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_6_US-006.log)
|
||||
[2026-01-19 15:03:46] SUCCESS: Story US-006 passed!
|
||||
[2026-01-19 15:03:46] Changes committed
|
||||
[2026-01-19 15:03:46] Progress: 6/16 stories completed
|
||||
[2026-01-19 15:03:48] === Iteration 7/20 ===
|
||||
[2026-01-19 15:03:48] Working on story: US-007
|
||||
[2026-01-19 15:03:48] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_7_US-007.log)
|
||||
[2026-01-19 15:07:05] SUCCESS: Story US-007 passed!
|
||||
[2026-01-19 15:07:05] Changes committed
|
||||
[2026-01-19 15:07:05] Progress: 7/16 stories completed
|
||||
[2026-01-19 15:07:07] === Iteration 8/20 ===
|
||||
[2026-01-19 15:07:07] Working on story: US-008
|
||||
[2026-01-19 15:07:07] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_8_US-008.log)
|
||||
[2026-01-19 15:10:46] SUCCESS: Story US-008 passed!
|
||||
[2026-01-19 15:10:46] Changes committed
|
||||
[2026-01-19 15:10:46] Progress: 8/16 stories completed
|
||||
[2026-01-19 15:10:48] === Iteration 9/20 ===
|
||||
[2026-01-19 15:10:48] Working on story: US-009
|
||||
[2026-01-19 15:10:48] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_9_US-009.log)
|
||||
[2026-01-19 15:16:49] SUCCESS: Story US-009 passed!
|
||||
[2026-01-19 15:16:49] Changes committed
|
||||
[2026-01-19 15:16:49] Progress: 9/16 stories completed
|
||||
[2026-01-19 15:16:51] === Iteration 10/20 ===
|
||||
[2026-01-19 15:16:51] Working on story: US-010
|
||||
[2026-01-19 15:16:51] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_10_US-010.log)
|
||||
[2026-01-19 15:20:11] SUCCESS: Story US-010 passed!
|
||||
[2026-01-19 15:20:11] Changes committed
|
||||
[2026-01-19 15:20:11] Progress: 10/16 stories completed
|
||||
[2026-01-19 15:20:13] === Iteration 11/20 ===
|
||||
[2026-01-19 15:20:13] Working on story: US-011
|
||||
[2026-01-19 15:20:13] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_11_US-011.log)
|
||||
[2026-01-19 15:25:07] SUCCESS: Story US-011 passed!
|
||||
[2026-01-19 15:25:07] Changes committed
|
||||
[2026-01-19 15:25:07] Progress: 11/16 stories completed
|
||||
[2026-01-19 15:25:09] === Iteration 12/20 ===
|
||||
[2026-01-19 15:25:09] Working on story: US-012
|
||||
[2026-01-19 15:25:09] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_12_US-012.log)
|
||||
[2026-01-19 15:30:34] SUCCESS: Story US-012 passed!
|
||||
[2026-01-19 15:30:34] Changes committed
|
||||
[2026-01-19 15:30:34] Progress: 12/16 stories completed
|
||||
[2026-01-19 15:30:36] === Iteration 13/20 ===
|
||||
[2026-01-19 15:30:36] Working on story: US-013
|
||||
[2026-01-19 15:30:36] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_13_US-013.log)
|
||||
[2026-01-19 15:32:34] SUCCESS: Story US-013 passed!
|
||||
[2026-01-19 15:32:34] Changes committed
|
||||
[2026-01-19 15:32:34] Progress: 13/16 stories completed
|
||||
[2026-01-19 15:32:36] === Iteration 14/20 ===
|
||||
[2026-01-19 15:32:36] Working on story: US-014
|
||||
[2026-01-19 15:32:36] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_14_US-014.log)
|
||||
[2026-01-19 15:35:05] SUCCESS: Story US-014 passed!
|
||||
[2026-01-19 15:35:05] Changes committed
|
||||
[2026-01-19 15:35:05] Progress: 14/16 stories completed
|
||||
[2026-01-19 15:35:07] === Iteration 15/20 ===
|
||||
[2026-01-19 15:35:07] Working on story: US-015
|
||||
[2026-01-19 15:35:07] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_15_US-015.log)
|
||||
[2026-01-19 15:38:49] SUCCESS: Story US-015 passed!
|
||||
[2026-01-19 15:38:49] Changes committed
|
||||
[2026-01-19 15:38:49] Progress: 15/16 stories completed
|
||||
[2026-01-19 15:38:51] === Iteration 16/20 ===
|
||||
[2026-01-19 15:38:51] Working on story: US-016
|
||||
[2026-01-19 15:38:51] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_16_US-016.log)
|
||||
[2026-01-19 15:42:40] SUCCESS: Story US-016 passed!
|
||||
@@ -1,67 +0,0 @@
|
||||
# Ralph Iteration Prompt Template
|
||||
|
||||
Acest fișier conține template-ul de prompt folosit de Ralph pentru fiecare iterație.
|
||||
|
||||
## Context
|
||||
|
||||
Ralph citește `prd.json` și selectează următoarea user story neprocesată (by priority).
|
||||
Apoi lansează Claude cu un prompt care include:
|
||||
|
||||
1. Story ID și titlu
|
||||
2. Acceptance criteria complete
|
||||
3. Technical notes
|
||||
4. CSS rules din PRD
|
||||
|
||||
## Cum funcționează
|
||||
|
||||
### Signals de la Claude:
|
||||
|
||||
- **STORY_PASSED** - Toate criteriile sunt îndeplinite, Ralph marchează story ca passed și commit-uiește
|
||||
- **STORY_BLOCKED: <reason>** - Story-ul nu poate fi completat, Ralph oprește loop-ul
|
||||
|
||||
### Flow per iterație:
|
||||
|
||||
```
|
||||
1. Ralph citește prd.json
|
||||
2. Selectează story cu priority minim care are passes=false
|
||||
3. Generează prompt cu detaliile story-ului
|
||||
4. Rulează: claude -p "<prompt>"
|
||||
5. Analizează output:
|
||||
- STORY_PASSED → mark passed, git commit, next iteration
|
||||
- STORY_BLOCKED → log reason, stop loop
|
||||
- Altceva → continue iteration (Claude încă lucrează)
|
||||
6. Sleep 2s, repeat
|
||||
```
|
||||
|
||||
## CSS Rules Reminder
|
||||
|
||||
Fiecare prompt include CSS rules din PRD pentru că sunt critice:
|
||||
|
||||
```
|
||||
IMPORTANT CSS RULES:
|
||||
- NEVER use hardcoded values - always use design tokens
|
||||
- Check docs/DESIGN_TOKENS.md before writing CSS
|
||||
- Test in BOTH light and dark mode
|
||||
- NEVER use :deep() in components
|
||||
```
|
||||
|
||||
## Manual Override
|
||||
|
||||
Dacă Ralph se blochează, poți:
|
||||
|
||||
1. Edita `prd.json` manual pentru a marca stories ca passed
|
||||
2. Adăuga notes explicative
|
||||
3. Relansa Ralph cu `./ralph.sh`
|
||||
|
||||
## Monitorizare
|
||||
|
||||
```bash
|
||||
# Vezi progress
|
||||
cat scripts/ralph/prd.json | jq '.userStories[] | {id, title, passes}'
|
||||
|
||||
# Vezi log curent
|
||||
tail -f scripts/ralph/progress.txt
|
||||
|
||||
# Vezi toate logurile
|
||||
ls -la scripts/ralph/logs/
|
||||
```
|
||||
@@ -1,203 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Ralph - Autonomous Loop for PRD Implementation
|
||||
# Usage: ./ralph.sh [max_iterations]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PRD_FILE="$SCRIPT_DIR/prd.json"
|
||||
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
|
||||
PROMPT_FILE="$SCRIPT_DIR/prompt.md"
|
||||
LOG_DIR="$SCRIPT_DIR/logs"
|
||||
|
||||
MAX_ITERATIONS=${1:-50}
|
||||
ITERATION=0
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${BLUE}[Ralph]${NC} $1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$PROGRESS_FILE"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[Ralph ERROR]${NC} $1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$PROGRESS_FILE"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[Ralph]${NC} $1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$PROGRESS_FILE"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
if [ ! -f "$PRD_FILE" ]; then
|
||||
error "prd.json not found at $PRD_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v claude &> /dev/null; then
|
||||
error "claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
error "jq not found. Install with: apt install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create log directory
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Get project info
|
||||
PROJECT_NAME=$(jq -r '.projectName' "$PRD_FILE")
|
||||
BRANCH_NAME=$(jq -r '.branchName' "$PRD_FILE")
|
||||
|
||||
log "Starting Ralph for project: $PROJECT_NAME"
|
||||
log "Max iterations: $MAX_ITERATIONS"
|
||||
|
||||
# Create branch if not exists
|
||||
cd "$PROJECT_ROOT"
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
if [ "$CURRENT_BRANCH" != "$BRANCH_NAME" ]; then
|
||||
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
|
||||
log "Switching to existing branch: $BRANCH_NAME"
|
||||
git checkout "$BRANCH_NAME"
|
||||
else
|
||||
log "Creating new branch: $BRANCH_NAME"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Function to get next pending story
|
||||
get_next_story() {
|
||||
jq -r '.userStories | map(select(.passes == false)) | sort_by(.priority) | .[0] | .id // empty' "$PRD_FILE"
|
||||
}
|
||||
|
||||
# Function to get story details
|
||||
get_story_details() {
|
||||
local story_id=$1
|
||||
jq -r --arg id "$story_id" '.userStories[] | select(.id == $id)' "$PRD_FILE"
|
||||
}
|
||||
|
||||
# Function to mark story as passed
|
||||
mark_story_passed() {
|
||||
local story_id=$1
|
||||
local notes=$2
|
||||
local tmp_file=$(mktemp)
|
||||
jq --arg id "$story_id" --arg notes "$notes" \
|
||||
'(.userStories[] | select(.id == $id)) |= (.passes = true | .notes = $notes)' \
|
||||
"$PRD_FILE" > "$tmp_file" && mv "$tmp_file" "$PRD_FILE"
|
||||
}
|
||||
|
||||
# Function to count stories
|
||||
count_stories() {
|
||||
local total=$(jq '.userStories | length' "$PRD_FILE")
|
||||
local passed=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
|
||||
echo "$passed/$total"
|
||||
}
|
||||
|
||||
# Main loop
|
||||
while [ $ITERATION -lt $MAX_ITERATIONS ]; do
|
||||
ITERATION=$((ITERATION + 1))
|
||||
|
||||
log "=== Iteration $ITERATION/$MAX_ITERATIONS ==="
|
||||
|
||||
# Get next story
|
||||
NEXT_STORY=$(get_next_story)
|
||||
|
||||
if [ -z "$NEXT_STORY" ]; then
|
||||
success "All stories completed! 🎉"
|
||||
break
|
||||
fi
|
||||
|
||||
log "Working on story: $NEXT_STORY"
|
||||
|
||||
# Get story details for prompt
|
||||
STORY_JSON=$(get_story_details "$NEXT_STORY")
|
||||
STORY_TITLE=$(echo "$STORY_JSON" | jq -r '.title')
|
||||
|
||||
# Create iteration prompt
|
||||
ITERATION_PROMPT="You are implementing user story $NEXT_STORY: $STORY_TITLE
|
||||
|
||||
Read the full PRD at scripts/ralph/prd.json for context and CSS rules.
|
||||
|
||||
Story details:
|
||||
$STORY_JSON
|
||||
|
||||
IMPORTANT CSS RULES (from PRD):
|
||||
- NEVER use hardcoded values - always use design tokens
|
||||
- Check docs/DESIGN_TOKENS.md and docs/CSS_PATTERNS.md before writing CSS
|
||||
- Test in BOTH light and dark mode
|
||||
- NEVER use :deep() in components
|
||||
|
||||
Your task:
|
||||
1. Implement this story following all acceptance criteria
|
||||
2. Run tests/typecheck to verify
|
||||
3. If ALL criteria pass, respond with: STORY_PASSED
|
||||
4. If blocked or need clarification, respond with: STORY_BLOCKED: <reason>
|
||||
|
||||
Do NOT move to other stories. Focus only on $NEXT_STORY."
|
||||
|
||||
# Run Claude
|
||||
LOG_FILE="$LOG_DIR/iteration_${ITERATION}_${NEXT_STORY}.log"
|
||||
log "Running Claude... (log: $LOG_FILE)"
|
||||
|
||||
# Run claude with the prompt (--output-format json avoids streaming mode issues)
|
||||
CLAUDE_OUTPUT=$(cd "$PROJECT_ROOT" && claude -p "$ITERATION_PROMPT" --output-format json 2>&1 | tee "$LOG_FILE")
|
||||
|
||||
# Check result
|
||||
if echo "$CLAUDE_OUTPUT" | grep -q "STORY_PASSED"; then
|
||||
success "Story $NEXT_STORY passed!"
|
||||
mark_story_passed "$NEXT_STORY" "Completed in iteration $ITERATION"
|
||||
|
||||
# Commit changes
|
||||
cd "$PROJECT_ROOT"
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add -A
|
||||
git commit -m "feat($PROJECT_NAME): Complete $NEXT_STORY - $STORY_TITLE
|
||||
|
||||
Implemented by Ralph autonomous loop.
|
||||
Iteration: $ITERATION
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
log "Changes committed"
|
||||
fi
|
||||
|
||||
elif echo "$CLAUDE_OUTPUT" | grep -q "STORY_BLOCKED"; then
|
||||
BLOCK_REASON=$(echo "$CLAUDE_OUTPUT" | grep "STORY_BLOCKED" | sed 's/STORY_BLOCKED://')
|
||||
error "Story $NEXT_STORY blocked: $BLOCK_REASON"
|
||||
log "Stopping loop due to blocked story"
|
||||
break
|
||||
else
|
||||
log "Story $NEXT_STORY not yet complete, continuing..."
|
||||
fi
|
||||
|
||||
# Progress update
|
||||
PROGRESS=$(count_stories)
|
||||
log "Progress: $PROGRESS stories completed"
|
||||
|
||||
# Small delay between iterations
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Final summary
|
||||
echo ""
|
||||
log "=== Ralph Session Complete ==="
|
||||
PROGRESS=$(count_stories)
|
||||
log "Final progress: $PROGRESS stories completed"
|
||||
log "Branch: $BRANCH_NAME"
|
||||
log "Logs: $LOG_DIR"
|
||||
|
||||
# Show remaining stories
|
||||
REMAINING=$(jq -r '[.userStories[] | select(.passes == false)] | length' "$PRD_FILE")
|
||||
if [ "$REMAINING" -gt 0 ]; then
|
||||
echo -e "\n${YELLOW}Remaining stories:${NC}"
|
||||
jq -r '.userStories[] | select(.passes == false) | " - \(.id): \(.title)"' "$PRD_FILE"
|
||||
fi
|
||||
Reference in New Issue
Block a user