feat(mobile-fixes-phase3): Complete US-305 - Tab-uri Clienți/Furnizori în Scadențe

Implemented by Ralph autonomous loop.
Iteration: 2

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 16:39:00 +00:00
parent edcdce58e2
commit 12e11fa44d
4 changed files with 141 additions and 14 deletions

View File

@@ -96,8 +96,8 @@
"MobileBottomNav activ", "MobileBottomNav activ",
"npm run build passes" "npm run build passes"
], ],
"passes": false, "passes": true,
"notes": "" "notes": "Completed in iteration 2"
}, },
{ {
"id": "US-301", "id": "US-301",

View File

@@ -1026,3 +1026,9 @@ User Stories: 11 (US-301 to US-311)
[2026-01-12 16:34:08] Working on story: US-304 [2026-01-12 16:34:08] Working on story: US-304
[2026-01-12 16:34:08] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_1_US-304.log) [2026-01-12 16:34:08] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_1_US-304.log)
[2026-01-12 16:36:25] SUCCESS: Story US-304 passed! [2026-01-12 16:36:25] SUCCESS: Story US-304 passed!
[2026-01-12 16:36:26] Changes committed
[2026-01-12 16:36:26] Progress: 3/11 stories completed
[2026-01-12 16:36:28] === Iteration 2/100 ===
[2026-01-12 16:36:28] Working on story: US-305
[2026-01-12 16:36:28] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_2_US-305.log)
[2026-01-12 16:39:00] SUCCESS: Story US-305 passed!

View File

@@ -45,9 +45,10 @@
<!-- Main Content --> <!-- Main Content -->
<div v-else class="card-content"> <div v-else class="card-content">
<!-- Nivel 1: Comparație Grafică --> <!-- Nivel 1: Comparație Grafică -->
<div class="comparison-section"> <!-- US-305: On mobile, show only the active tab's content -->
<!-- Clienți Side --> <div class="comparison-section" :class="{ 'mobile-single-view': props.isMobile }">
<div class="comparison-side clients"> <!-- Clienți Side - Hidden on mobile when suppliers tab active -->
<div class="comparison-side clients" v-if="!props.isMobile || props.activeTab === 'clients'">
<div class="side-header"> <div class="side-header">
<span class="side-label">Clienți - De încasat</span> <span class="side-label">Clienți - De încasat</span>
<span class="side-total">{{ formatCurrency(clientsTotal) }}</span> <span class="side-total">{{ formatCurrency(clientsTotal) }}</span>
@@ -86,11 +87,11 @@
</div> </div>
</div> </div>
<!-- Divider --> <!-- Divider - Hidden on mobile -->
<div class="comparison-divider"></div> <div v-if="!props.isMobile" class="comparison-divider"></div>
<!-- Furnizori Side --> <!-- Furnizori Side - Hidden on mobile when clients tab active -->
<div class="comparison-side suppliers"> <div class="comparison-side suppliers" v-if="!props.isMobile || props.activeTab === 'suppliers'">
<div class="side-header"> <div class="side-header">
<span class="side-label">Furnizori - De plătit</span> <span class="side-label">Furnizori - De plătit</span>
<span class="side-total">{{ formatCurrency(suppliersTotal) }}</span> <span class="side-total">{{ formatCurrency(suppliersTotal) }}</span>
@@ -512,6 +513,17 @@ const props = defineProps({
type: [Number, String], type: [Number, String],
required: true, required: true,
}, },
// US-305: Active tab for mobile view (clients/suppliers)
activeTab: {
type: String,
default: 'clients',
validator: (value) => ['clients', 'suppliers'].includes(value),
},
// US-305: Is mobile view
isMobile: {
type: Boolean,
default: false,
},
}); });
// Emits // Emits
@@ -1689,14 +1701,24 @@ onMounted(() => {
display: table; display: table;
} }
/* US-305: Mobile single view - show only active tab */
.comparison-section.mobile-single-view {
grid-template-columns: 1fr;
gap: 0;
}
.comparison-section.mobile-single-view .comparison-side {
width: 100%;
}
/* Responsive - Tablet */ /* Responsive - Tablet */
@media (max-width: 1024px) { @media (max-width: 1024px) {
.comparison-section { .comparison-section:not(.mobile-single-view) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1.5rem; gap: 1.5rem;
} }
.comparison-divider { .comparison-section:not(.mobile-single-view) .comparison-divider {
display: none; display: none;
} }
} }

View File

@@ -2,12 +2,32 @@
<!-- Mobile Top Bar --> <!-- Mobile Top Bar -->
<MobileTopBar <MobileTopBar
v-if="isMobile" v-if="isMobile"
title="Analiză Scadențe" title="Scadențe"
:show-back="true" :show-back="true"
@back-click="goBack" @back-click="goBack"
/> />
<main class="main-content" :class="{ 'mobile-layout': isMobile }"> <!-- US-305: Mobile Tabs for Clienți/Furnizori -->
<div v-if="isMobile" class="mobile-tabs-container">
<div class="mobile-tabs">
<button
class="mobile-tab"
:class="{ active: activeTab === 'clients' }"
@click="switchTab('clients')"
>
<span class="tab-label">Clienți</span>
</button>
<button
class="mobile-tab"
:class="{ active: activeTab === 'suppliers' }"
@click="switchTab('suppliers')"
>
<span class="tab-label">Furnizori</span>
</button>
</div>
</div>
<main class="main-content" :class="{ 'mobile-layout': isMobile, 'has-tabs': isMobile }">
<div class="app-container"> <div class="app-container">
<!-- Page Header - only on desktop --> <!-- Page Header - only on desktop -->
<div v-if="!isMobile" class="page-header"> <div v-if="!isMobile" class="page-header">
@@ -34,6 +54,8 @@
<div v-else class="maturity-container"> <div v-else class="maturity-container">
<MaturityAndDetailsCard <MaturityAndDetailsCard
:companyId="companyStore.selectedCompany?.id_firma" :companyId="companyStore.selectedCompany?.id_firma"
:activeTab="activeTab"
:isMobile="isMobile"
@periodChanged="handlePeriodChange" @periodChanged="handlePeriodChange"
/> />
</div> </div>
@@ -46,7 +68,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import Button from 'primevue/button' import Button from 'primevue/button'
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue' import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue' import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
@@ -54,8 +76,12 @@ import MaturityAndDetailsCard from '@reports/components/dashboard/cards/Maturity
import { useCompanyStore } from '@reports/stores/sharedStores' import { useCompanyStore } from '@reports/stores/sharedStores'
const router = useRouter() const router = useRouter()
const route = useRoute()
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
// US-305: Tab state synced with URL query params
const activeTab = ref(route.query.tab === 'suppliers' ? 'suppliers' : 'clients')
// Detectare mobile - reactive with resize listener // Detectare mobile - reactive with resize listener
const windowWidth = ref(window.innerWidth) const windowWidth = ref(window.innerWidth)
const isMobile = computed(() => windowWidth.value < 768) const isMobile = computed(() => windowWidth.value < 768)
@@ -70,6 +96,22 @@ const goBack = () => {
router.push('/reports/dashboard') router.push('/reports/dashboard')
} }
// US-305: Switch between Clienți/Furnizori tabs
// Updates URL query param without full navigation, preserves filters
const switchTab = (tab) => {
if (tab === activeTab.value) return
activeTab.value = tab
// Update URL query param without full navigation
router.replace({
query: {
...route.query,
tab: tab === 'suppliers' ? 'suppliers' : undefined // Remove param if 'clients' (default)
}
})
}
// Handle period change from MaturityAndDetailsCard // Handle period change from MaturityAndDetailsCard
const handlePeriodChange = (period) => { const handlePeriodChange = (period) => {
console.log('Maturity period changed:', period) console.log('Maturity period changed:', period)
@@ -95,6 +137,63 @@ onUnmounted(() => {
padding-bottom: 56px; padding-bottom: 56px;
} }
/* Mobile layout with tabs - extra padding for tab bar */
.main-content.mobile-layout.has-tabs {
padding-top: calc(56px + 48px); /* MobileTopBar + tabs height */
}
/* ================================================
US-305: Mobile Tabs (Clienți/Furnizori)
Material Design 3 inspired full-width tabs
================================================ */
.mobile-tabs-container {
position: fixed;
top: 56px; /* Below MobileTopBar */
left: 0;
right: 0;
z-index: var(--z-sticky);
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
}
.mobile-tabs {
display: flex;
width: 100%;
}
.mobile-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-md);
min-height: 48px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-color-secondary);
font-size: var(--text-sm);
font-weight: var(--font-medium);
}
.mobile-tab:active {
background: var(--surface-hover);
}
.mobile-tab.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
font-weight: var(--font-semibold);
}
.tab-label {
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* App container */ /* App container */
.app-container { .app-container {
max-width: 1400px; max-width: 1400px;