feat(unified-mobile-material-design): Complete US-103 - Refactor ReceiptsListView să folosească componente comune
Implemented by Ralph autonomous loop. Iteration: 5 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -45,67 +45,18 @@
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- US-040: Mobile Android-Native Top Bar -->
|
||||
<header v-if="isMobile" class="mobile-top-bar" :class="{ 'selection-active': mobileSelectionMode }">
|
||||
<div class="top-bar-left">
|
||||
<Button
|
||||
v-if="mobileSelectionMode"
|
||||
icon="pi pi-times"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
@click="exitMobileSelectionMode"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
icon="pi pi-bars"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
@click="toggleMobileMenu"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="top-bar-title">
|
||||
{{ mobileSelectionMode ? `${selectedReceipts.length} selectate` : 'Bonuri' }}
|
||||
</h1>
|
||||
<div class="top-bar-right">
|
||||
<template v-if="mobileSelectionMode">
|
||||
<Button
|
||||
icon="pi pi-check-square"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
@click="selectAllMobile"
|
||||
v-tooltip.bottom="'Selectează tot'"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button
|
||||
icon="pi pi-search"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
@click="showFilters = !showFilters"
|
||||
:class="{ 'active': showFilters }"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-filter"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
:class="{ 'active': hasActiveFilters }"
|
||||
@click="showFilters = !showFilters"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-ellipsis-v"
|
||||
text
|
||||
rounded
|
||||
class="top-bar-btn"
|
||||
@click="toggleMoreMenu"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
<!-- US-103: Mobile Android-Native Top Bar (using shared component) -->
|
||||
<MobileTopBar
|
||||
v-if="isMobile"
|
||||
:title="mobileSelectionMode ? `${selectedReceipts.length} selectate` : 'Bonuri'"
|
||||
:show-back="mobileSelectionMode"
|
||||
:show-menu="!mobileSelectionMode"
|
||||
:selection-active="mobileSelectionMode"
|
||||
:actions="mobileTopBarActions"
|
||||
@back-click="exitMobileSelectionMode"
|
||||
@menu-click="toggleMobileMenu"
|
||||
@action-click="handleTopBarAction"
|
||||
/>
|
||||
|
||||
<!-- US-040: Mobile Filter Chips (horizontal scrollable) -->
|
||||
<div v-if="isMobile && stats && !mobileSelectionMode" class="mobile-filter-chips-container">
|
||||
@@ -603,18 +554,11 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- US-039: Mobile Selection Bottom Bar -->
|
||||
<Transition name="slide-up">
|
||||
<div v-if="mobileSelectionMode && selectedReceipts.length > 0" class="mobile-selection-bottom-bar">
|
||||
<Button
|
||||
label="Șterge"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
class="delete-btn-mobile"
|
||||
@click="confirmBulkDelete"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
<!-- US-103: Mobile Selection Bottom Bar (using shared component) -->
|
||||
<MobileSelectionFooter
|
||||
:visible="mobileSelectionMode && selectedReceipts.length > 0"
|
||||
:actions="mobileSelectionActions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- US-040: Mobile FAB (Floating Action Button) -->
|
||||
@@ -629,25 +573,12 @@
|
||||
</button>
|
||||
</Transition>
|
||||
|
||||
<!-- US-040: Mobile Bottom Navigation -->
|
||||
<nav v-if="isMobile && !mobileSelectionMode" class="mobile-bottom-nav">
|
||||
<router-link to="/data-entry" class="bottom-nav-item active">
|
||||
<i class="pi pi-receipt"></i>
|
||||
<span>Bonuri</span>
|
||||
</router-link>
|
||||
<button class="bottom-nav-item" @click="openBulkFileInput">
|
||||
<i class="pi pi-cloud-upload"></i>
|
||||
<span>Upload</span>
|
||||
</button>
|
||||
<router-link to="/reports/dashboard" class="bottom-nav-item">
|
||||
<i class="pi pi-chart-bar"></i>
|
||||
<span>Rapoarte</span>
|
||||
</router-link>
|
||||
<router-link to="/data-entry/ocr-metrics" class="bottom-nav-item">
|
||||
<i class="pi pi-cog"></i>
|
||||
<span>Setări</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<!-- US-103: Mobile Bottom Navigation (using shared component) -->
|
||||
<MobileBottomNav
|
||||
v-if="isMobile && !mobileSelectionMode"
|
||||
:items="mobileBottomNavItems"
|
||||
@item-click="handleBottomNavClick"
|
||||
/>
|
||||
|
||||
<!-- Desktop: Compact Data Table with Batch Grouping (US-002) -->
|
||||
<div v-else class="data-table-container">
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user