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);