feat(unified-mobile-desktop-ui): Complete US-513 - Analize Scadențe - Eliminare Secțiune Facturi Detaliate

Implemented by Ralph autonomous loop.
Iteration: 14

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 23:11:56 +00:00
parent 004951ec50
commit 870ef5175d
3 changed files with 22 additions and 299 deletions

View File

@@ -1,85 +1,13 @@
<template>
<!-- US-501: Mobile Top Bar with unified header actions -->
<!-- US-513: Simplified Mobile Top Bar - no filters/export since no detailed data -->
<MobileTopBar
v-if="isMobile"
title="Scadențe"
:show-back="true"
:actions="mobileTopBarActions"
@back-click="goBack"
@action-click="handleTopBarAction"
/>
<!-- US-501: Export Menu (for mobile export dropdown) -->
<Menu ref="exportMenu" :model="exportMenuItems" :popup="true" />
<!-- US-306: Filter BottomSheet for mobile -->
<BottomSheet v-model="showFilters">
<h3 class="bottom-sheet-title">Filtre</h3>
<div class="bottom-sheet-filters">
<!-- Period Filter -->
<div class="form-group">
<label class="form-label">Perioadă</label>
<Dropdown
v-model="filterPeriod"
:options="periodOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați perioada"
class="w-full"
/>
</div>
<!-- Type Filter -->
<div class="form-group">
<label class="form-label">Tip</label>
<Dropdown
v-model="filterType"
:options="typeOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați tipul"
class="w-full"
/>
</div>
<!-- Bottom sheet actions -->
<div class="bottom-sheet-actions">
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
@click="resetFilters"
/>
<Button
icon="pi pi-check"
label="Aplică"
@click="applyFilters"
/>
</div>
</div>
</BottomSheet>
<!-- 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 }">
<main class="main-content" :class="{ 'mobile-layout': isMobile }">
<div class="app-container">
<!-- Page Header - only on desktop -->
<div v-if="!isMobile" class="page-header">
@@ -102,157 +30,43 @@
/>
</div>
<!-- Main content - MaturityAndDetailsCard -->
<!-- Main content - MaturityAnalysisCard (US-513: doar analiza scadențelor, fără facturi detaliate) -->
<div v-else class="maturity-container">
<MaturityAndDetailsCard
<MaturityAnalysisCard
ref="maturityCardRef"
:companyId="companyStore.selectedCompany?.id_firma"
:activeTab="activeTab"
:isMobile="isMobile"
@periodChanged="handlePeriodChange"
/>
</div>
</div>
</main>
<!-- Mobile Bottom Nav - US-307: Using default nav items -->
<!-- Mobile Bottom Nav -->
<MobileBottomNav v-if="isMobile" />
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useRouter } from 'vue-router'
import Button from 'primevue/button'
import Dropdown from 'primevue/dropdown'
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
import BottomSheet from '@shared/components/mobile/BottomSheet.vue'
import Menu from 'primevue/menu'
import MaturityAndDetailsCard from '@reports/components/dashboard/cards/MaturityAndDetailsCard.vue'
import MaturityAnalysisCard from '@reports/components/dashboard/cards/MaturityAnalysisCard.vue'
import { useCompanyStore } from '@reports/stores/sharedStores'
const router = useRouter()
const route = useRoute()
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
const windowWidth = ref(window.innerWidth)
const isMobile = computed(() => windowWidth.value < 768)
// US-306: Ref to MaturityAndDetailsCard for calling exposed methods
// Ref to MaturityAnalysisCard
const maturityCardRef = ref(null)
// US-306: Mobile filter state
const showFilters = ref(false)
const filterPeriod = ref('1m')
const filterType = ref('clients')
// US-306: Filter options
const periodOptions = [
{ label: '7 zile', value: '7d' },
{ label: '1 lună', value: '1m' },
{ label: '3 luni', value: '3m' },
{ label: '6 luni', value: '6m' },
{ label: '12 luni', value: '12m' },
{ label: 'Toate', value: 'all' }
]
const typeOptions = [
{ label: 'Clienți', value: 'clients' },
{ label: 'Furnizori', value: 'suppliers' },
{ label: 'Trezorerie', value: 'treasury' }
]
// US-306: Check if filters have non-default values
const hasActiveFilters = computed(() => {
return filterPeriod.value !== '1m' || filterType.value !== 'clients'
})
// US-501: Mobile TopBar actions (filter, reset, export dropdown)
const mobileTopBarActions = computed(() => [
{
icon: 'pi pi-filter',
label: 'Filtre',
tooltip: 'Filtre',
active: hasActiveFilters.value
},
{
icon: 'pi pi-filter-slash',
label: 'Resetează',
tooltip: 'Resetează Filtrele'
},
{
icon: 'pi pi-download',
label: 'Export',
tooltip: 'Export'
}
])
// US-501: Export menu ref for mobile
const exportMenu = ref(null)
const exportMenuItems = ref([
{
label: 'Export PDF',
icon: 'pi pi-file-pdf',
command: () => {
if (maturityCardRef.value) {
maturityCardRef.value.exportPDF()
}
}
},
{
label: 'Export XLSX',
icon: 'pi pi-file-excel',
command: () => {
if (maturityCardRef.value) {
maturityCardRef.value.exportExcel()
}
}
}
])
// US-501: Handle top bar action clicks
const handleTopBarAction = (action, event) => {
if (action.icon === 'pi pi-filter') {
// Sync current values from child component before opening
if (maturityCardRef.value) {
filterPeriod.value = maturityCardRef.value.selectedPeriod
filterType.value = maturityCardRef.value.selectedType
}
showFilters.value = !showFilters.value
} else if (action.icon === 'pi pi-filter-slash') {
resetFilters()
} else if (action.icon === 'pi pi-download') {
exportMenu.value.toggle(event)
}
}
// US-306: Apply filters to child component
const applyFilters = () => {
if (maturityCardRef.value) {
maturityCardRef.value.selectedPeriod = filterPeriod.value
maturityCardRef.value.selectedType = filterType.value
maturityCardRef.value.loadDetailedData()
}
showFilters.value = false
}
// US-306: Reset filters to defaults
const resetFilters = () => {
filterPeriod.value = '1m'
filterType.value = 'clients'
applyFilters()
}
// Handle window resize for mobile detection
const handleResize = () => {
windowWidth.value = window.innerWidth
if (!isMobile.value) {
showFilters.value = false // Reset when switching to desktop
}
}
// Navigation
@@ -260,30 +74,11 @@ const goBack = () => {
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 MaturityAnalysisCard
const handlePeriodChange = (period) => {
console.log('Maturity period changed:', period)
}
// Mobile navigation items
// US-307: Removed custom mobileNavItems - using MobileBottomNav defaults
// Lifecycle
onMounted(() => {
window.addEventListener('resize', handleResize)
@@ -301,63 +96,6 @@ onUnmounted(() => {
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 {
max-width: 1400px;
@@ -434,30 +172,4 @@ onUnmounted(() => {
.empty-action {
margin-top: var(--space-md);
}
/* ================================================
US-306: Bottom Sheet Styles
================================================ */
.bottom-sheet-title {
font-size: var(--text-lg);
font-weight: var(--font-semibold);
color: var(--text-color);
margin: 0 0 var(--space-md) 0;
}
.bottom-sheet-filters {
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.bottom-sheet-actions {
display: flex;
gap: var(--space-sm);
justify-content: flex-end;
margin-top: var(--space-md);
padding-top: var(--space-md);
border-top: 1px solid var(--surface-border);
}
</style>