From fb84cbc4f07205885f077f97a7f6083f22c713c7 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Mon, 12 Jan 2026 09:55:34 +0000 Subject: [PATCH] =?UTF-8?q?feat(unified-mobile-material-design):=20Complet?= =?UTF-8?q?e=20US-103=20-=20Refactor=20ReceiptsListView=20s=C4=83=20folose?= =?UTF-8?q?asc=C4=83=20componente=20comune?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented by Ralph autonomous loop. Iteration: 5 Co-Authored-By: Claude --- scripts/ralph/prd.json | 4 +- scripts/ralph/progress.txt | 6 + .../views/receipts/ReceiptsListView.vue | 432 ++++-------------- 3 files changed, 108 insertions(+), 334 deletions(-) diff --git a/scripts/ralph/prd.json b/scripts/ralph/prd.json index 69f1823..346eec6 100644 --- a/scripts/ralph/prd.json +++ b/scripts/ralph/prd.json @@ -104,8 +104,8 @@ "Verify in browser că lista bonuri funcționează identic pe mobil", "npm run build passes" ], - "passes": false, - "notes": "" + "passes": true, + "notes": "Completed in iteration 5" }, { "id": "US-104", diff --git a/scripts/ralph/progress.txt b/scripts/ralph/progress.txt index 73da259..cd79c7b 100644 --- a/scripts/ralph/progress.txt +++ b/scripts/ralph/progress.txt @@ -31,3 +31,9 @@ Mon Jan 12 09:44:54 AM UTC 2026 [2026-01-12 09:49:17] Working on story: US-102 [2026-01-12 09:49:17] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_4_US-102.log) [2026-01-12 09:50:55] SUCCESS: Story US-102 passed! +[2026-01-12 09:50:55] Changes committed +[2026-01-12 09:50:55] Progress: 4/20 stories completed +[2026-01-12 09:50:57] === Iteration 5/100 === +[2026-01-12 09:50:57] Working on story: US-103 +[2026-01-12 09:50:57] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_5_US-103.log) +[2026-01-12 09:55:34] SUCCESS: Story US-103 passed! diff --git a/src/modules/data-entry/views/receipts/ReceiptsListView.vue b/src/modules/data-entry/views/receipts/ReceiptsListView.vue index 9fb5b46..7540802 100644 --- a/src/modules/data-entry/views/receipts/ReceiptsListView.vue +++ b/src/modules/data-entry/views/receipts/ReceiptsListView.vue @@ -45,67 +45,18 @@ - -
-
-
-

- {{ mobileSelectionMode ? `${selectedReceipts.length} selectate` : 'Bonuri' }} -

-
- - -
-
+ +
@@ -603,18 +554,11 @@ />
- - -
-
-
+ + @@ -629,25 +573,12 @@ - - + +
@@ -1048,6 +979,10 @@ import Dropdown from 'primevue/dropdown' import Checkbox from 'primevue/checkbox' import Sidebar from 'primevue/sidebar' // US-040: Mobile hamburger menu import DragDropOverlay from '@data-entry/components/bulk/DragDropOverlay.vue' +// US-103: Mobile Material Design common components +import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue' +import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue' +import MobileSelectionFooter from '@shared/components/mobile/MobileSelectionFooter.vue' import BatchGroupHeader from '@data-entry/components/bulk/BatchGroupHeader.vue' import ProcessingStatusCell from '@data-entry/components/bulk/ProcessingStatusCell.vue' import Paginator from 'primevue/paginator' @@ -1155,6 +1090,72 @@ const moreMenuItems = computed(() => [ } ]) +// US-103: Top bar actions for MobileTopBar component +const mobileTopBarActions = computed(() => { + if (mobileSelectionMode.value) { + // Selection mode - show select all action + return [ + { id: 'select-all', icon: 'pi pi-check-square', label: 'Selectează tot', tooltip: 'Selectează tot' } + ] + } + // Normal mode - show search, filter, more menu + return [ + { id: 'search', icon: 'pi pi-search', active: showFilters.value, tooltip: 'Căutare' }, + { id: 'filter', icon: 'pi pi-filter', active: hasActiveFilters.value, tooltip: 'Filtre' }, + { id: 'more', icon: 'pi pi-ellipsis-v', tooltip: 'Mai multe' } + ] +}) + +// US-103: Handle top bar action clicks +const handleTopBarAction = (action) => { + switch (action.id) { + case 'select-all': + selectAllMobile() + break + case 'search': + case 'filter': + showFilters.value = !showFilters.value + break + case 'more': + // The more menu needs to be toggled with the event, but we don't have the event here + // So we need to use a different approach - we'll keep using toggleMoreMenu directly + // by storing a ref to trigger it + if (moreMenuRef.value) { + // Create a synthetic event at the more button position + const btn = document.querySelector('.mobile-top-bar .top-bar-btn:last-child') + if (btn) { + moreMenuRef.value.toggle({ currentTarget: btn }) + } + } + break + } +} + +// US-103: Bottom nav items for MobileBottomNav component +const mobileBottomNavItems = computed(() => [ + { to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri', active: true }, + { icon: 'pi pi-cloud-upload', label: 'Upload' }, // No 'to' - handled via item-click + { to: '/reports/dashboard', icon: 'pi pi-chart-bar', label: 'Rapoarte' }, + { to: '/data-entry/ocr-metrics', icon: 'pi pi-cog', label: 'Setări' } +]) + +// US-103: Handle bottom nav clicks for items without routes +const handleBottomNavClick = (item) => { + if (item.label === 'Upload') { + openBulkFileInput() + } +} + +// US-103: Selection footer actions for MobileSelectionFooter component +const mobileSelectionActions = computed(() => [ + { + label: 'Șterge', + icon: 'pi pi-trash', + severity: 'danger', + handler: () => confirmBulkDelete() + } +]) + // US-040: Handle scroll to show/hide FAB const handleScroll = () => { if (!isMobile.value) return @@ -3322,68 +3323,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { } } -/* US-039: Mobile Selection Bottom Bar */ -.mobile-selection-bottom-bar { - position: fixed; - bottom: 0; - left: 0; - right: 0; - background: var(--surface-card); - border-top: 1px solid var(--surface-border); - padding: var(--space-md); - display: flex; - justify-content: center; - align-items: center; - z-index: var(--z-fixed); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); -} - -.mobile-selection-bottom-bar .delete-btn-mobile { - width: 100%; - max-width: 400px; - height: 48px; - font-size: var(--text-base); - font-weight: var(--font-semibold); -} - -/* Slide-up animation for bottom bar */ -.slide-up-enter-active, -.slide-up-leave-active { - transition: transform var(--transition-normal), opacity var(--transition-normal); -} - -.slide-up-enter-from, -.slide-up-leave-to { - transform: translateY(100%); - opacity: 0; -} - -.slide-up-enter-to, -.slide-up-leave-from { - transform: translateY(0); - opacity: 1; -} - -/* Dark mode for bottom bar */ -[data-theme="dark"] .mobile-selection-bottom-bar { - background: var(--surface-card); - border-top-color: var(--surface-border); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); -} - -/* Auto dark mode (system preference) for bottom bar */ -@media (prefers-color-scheme: dark) { - :root:not([data-theme]) .mobile-selection-bottom-bar { - background: var(--surface-card); - border-top-color: var(--surface-border); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); - } -} - -/* Add padding to receipt cards when bottom bar is visible to prevent overlap */ -.receipt-cards:has(.mobile-selection-bottom-bar) { - padding-bottom: 80px; -} +/* US-103: Mobile Selection Bottom Bar styles now in MobileSelectionFooter.vue */ .card-row-1 { display: flex; @@ -4287,58 +4227,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { padding: var(--space-sm); } -/* Mobile Top Bar - Android native style */ -.mobile-top-bar { - position: fixed; - top: 0; - left: 0; - right: 0; - height: 56px; - background: var(--surface-card); - border-bottom: 1px solid var(--surface-border); - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 var(--space-xs); - z-index: 1000; - box-shadow: var(--shadow-sm); -} - -.mobile-top-bar.selection-active { - background: var(--blue-50); - border-bottom-color: var(--blue-200); -} - -.top-bar-left, -.top-bar-right { - display: flex; - align-items: center; - gap: var(--space-xs); -} - -.top-bar-btn { - width: 48px; - height: 48px; - border-radius: var(--radius-full); - color: var(--text-color); -} - -.top-bar-btn.active { - color: var(--color-primary); - background: var(--blue-50); -} - -.top-bar-title { - font-size: var(--text-lg); - font-weight: var(--font-semibold); - color: var(--text-color); - margin: 0; - flex: 1; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* US-103: Mobile Top Bar styles now in MobileTopBar.vue */ /* Mobile Filter Chips Container */ .mobile-filter-chips-container { @@ -4445,58 +4334,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { font-size: var(--text-xs); } -/* Mobile Bottom Navigation - Android native style */ -.mobile-bottom-nav { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 56px; - background: var(--surface-card); - border-top: 1px solid var(--surface-border); - display: flex; - align-items: stretch; - justify-content: space-around; - z-index: 1000; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08); -} - -.bottom-nav-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--space-xs); - color: var(--text-color-secondary); - text-decoration: none; - font-size: var(--text-xs); - font-weight: var(--font-medium); - background: none; - border: none; - cursor: pointer; - transition: color var(--transition-fast); - padding: var(--space-xs); - min-width: 48px; -} - -.bottom-nav-item i { - font-size: var(--text-xl); -} - -.bottom-nav-item:active { - background: var(--surface-hover); -} - -.bottom-nav-item.active, -.bottom-nav-item.router-link-active { - color: var(--color-primary); -} - -.bottom-nav-item.active i, -.bottom-nav-item.router-link-active i { - color: var(--color-primary); -} +/* US-103: Mobile Bottom Navigation styles now in MobileBottomNav.vue */ /* Mobile FAB - Floating Action Button */ .mobile-fab { @@ -4612,25 +4450,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { padding-bottom: 80px; /* Space for FAB and bottom nav */ } -/* Dark mode support for US-040 components */ -[data-theme="dark"] .mobile-top-bar { - background: var(--surface-card); - border-bottom-color: var(--surface-border); -} - -[data-theme="dark"] .mobile-top-bar.selection-active { - background: var(--blue-900); - border-bottom-color: var(--blue-700); -} - -[data-theme="dark"] .top-bar-btn { - color: var(--text-color); -} - -[data-theme="dark"] .top-bar-btn.active { - color: var(--blue-400); - background: var(--blue-900); -} +/* US-103: Dark mode for MobileTopBar now in component */ [data-theme="dark"] .mobile-filter-chips-container { background: var(--surface-card); @@ -4661,24 +4481,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { border-color: var(--red-700); } -[data-theme="dark"] .mobile-bottom-nav { - background: var(--surface-card); - border-top-color: var(--surface-border); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); -} - -[data-theme="dark"] .bottom-nav-item { - color: var(--text-color-secondary); -} - -[data-theme="dark"] .bottom-nav-item:active { - background: var(--surface-hover); -} - -[data-theme="dark"] .bottom-nav-item.active, -[data-theme="dark"] .bottom-nav-item.router-link-active { - color: var(--blue-400); -} +/* US-103: Dark mode for MobileBottomNav now in component */ [data-theme="dark"] .mobile-fab { background: var(--blue-600); @@ -4692,26 +4495,8 @@ const cleanupCompletedBatches = (storedBatchIds) => { } /* System preference dark mode support */ +/* US-103: Auto dark mode for MobileTopBar now in component */ @media (prefers-color-scheme: dark) { - :root:not([data-theme]) .mobile-top-bar { - background: var(--surface-card); - border-bottom-color: var(--surface-border); - } - - :root:not([data-theme]) .mobile-top-bar.selection-active { - background: var(--blue-900); - border-bottom-color: var(--blue-700); - } - - :root:not([data-theme]) .top-bar-btn { - color: var(--text-color); - } - - :root:not([data-theme]) .top-bar-btn.active { - color: var(--blue-400); - background: var(--blue-900); - } - :root:not([data-theme]) .mobile-filter-chips-container { background: var(--surface-card); border-bottom-color: var(--surface-border); @@ -4741,24 +4526,7 @@ const cleanupCompletedBatches = (storedBatchIds) => { border-color: var(--red-700); } - :root:not([data-theme]) .mobile-bottom-nav { - background: var(--surface-card); - border-top-color: var(--surface-border); - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); - } - - :root:not([data-theme]) .bottom-nav-item { - color: var(--text-color-secondary); - } - - :root:not([data-theme]) .bottom-nav-item:active { - background: var(--surface-hover); - } - - :root:not([data-theme]) .bottom-nav-item.active, - :root:not([data-theme]) .bottom-nav-item.router-link-active { - color: var(--blue-400); - } + /* US-103: Auto dark mode for MobileBottomNav now in component */ :root:not([data-theme]) .mobile-fab { background: var(--blue-600);