feat(mobile-fixes-phase3): Complete US-306 - Restaurare Butoane Export și Filtrare pe Rapoarte
Implemented by Ralph autonomous loop. Iteration: 6 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -161,8 +161,8 @@
|
|||||||
"Click pe Filtrare deschide BottomSheet cu filtrele paginii",
|
"Click pe Filtrare deschide BottomSheet cu filtrele paginii",
|
||||||
"npm run build passes"
|
"npm run build passes"
|
||||||
],
|
],
|
||||||
"passes": false,
|
"passes": true,
|
||||||
"notes": ""
|
"notes": "Completed in iteration 6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-309",
|
"id": "US-309",
|
||||||
|
|||||||
@@ -1050,3 +1050,9 @@ User Stories: 11 (US-301 to US-311)
|
|||||||
[2026-01-12 16:43:01] Working on story: US-303
|
[2026-01-12 16:43:01] Working on story: US-303
|
||||||
[2026-01-12 16:43:01] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_5_US-303.log)
|
[2026-01-12 16:43:01] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_5_US-303.log)
|
||||||
[2026-01-12 16:46:26] SUCCESS: Story US-303 passed!
|
[2026-01-12 16:46:26] SUCCESS: Story US-303 passed!
|
||||||
|
[2026-01-12 16:46:26] Changes committed
|
||||||
|
[2026-01-12 16:46:26] Progress: 7/11 stories completed
|
||||||
|
[2026-01-12 16:46:28] === Iteration 6/100 ===
|
||||||
|
[2026-01-12 16:46:28] Working on story: US-306
|
||||||
|
[2026-01-12 16:46:28] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_6_US-306.log)
|
||||||
|
[2026-01-12 16:51:00] SUCCESS: Story US-306 passed!
|
||||||
|
|||||||
@@ -1117,7 +1117,7 @@ const toggleFabMenu = (event) => {
|
|||||||
fabMenuRef.value?.toggle(event)
|
fabMenuRef.value?.toggle(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// US-103: Top bar actions for MobileTopBar component
|
// US-103/US-306: Top bar actions for MobileTopBar component
|
||||||
const mobileTopBarActions = computed(() => {
|
const mobileTopBarActions = computed(() => {
|
||||||
if (mobileSelectionMode.value) {
|
if (mobileSelectionMode.value) {
|
||||||
// Selection mode - show select all action
|
// Selection mode - show select all action
|
||||||
@@ -1125,24 +1125,27 @@ const mobileTopBarActions = computed(() => {
|
|||||||
{ id: 'select-all', icon: 'pi pi-check-square', label: 'Selectează tot', tooltip: 'Selectează tot' }
|
{ id: 'select-all', icon: 'pi pi-check-square', label: 'Selectează tot', tooltip: 'Selectează tot' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// Normal mode - show search, filter, more menu
|
// Normal mode - show filter, export, more menu (US-306)
|
||||||
return [
|
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: 'filter', icon: 'pi pi-filter', active: hasActiveFilters.value, tooltip: 'Filtre' },
|
||||||
|
{ id: 'export', icon: 'pi pi-download', tooltip: 'Export Excel' },
|
||||||
{ id: 'more', icon: 'pi pi-ellipsis-v', tooltip: 'Mai multe' }
|
{ id: 'more', icon: 'pi pi-ellipsis-v', tooltip: 'Mai multe' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// US-103: Handle top bar action clicks
|
// US-103/US-306: Handle top bar action clicks
|
||||||
const handleTopBarAction = (action) => {
|
const handleTopBarAction = (action) => {
|
||||||
switch (action.id) {
|
switch (action.id) {
|
||||||
case 'select-all':
|
case 'select-all':
|
||||||
selectAllMobile()
|
selectAllMobile()
|
||||||
break
|
break
|
||||||
case 'search':
|
|
||||||
case 'filter':
|
case 'filter':
|
||||||
showFilters.value = !showFilters.value
|
showFilters.value = !showFilters.value
|
||||||
break
|
break
|
||||||
|
case 'export':
|
||||||
|
// US-306: Export all current receipts
|
||||||
|
exportAllReceipts()
|
||||||
|
break
|
||||||
case 'more':
|
case 'more':
|
||||||
// The more menu needs to be toggled with the event, but we don't have the event here
|
// 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
|
// So we need to use a different approach - we'll keep using toggleMoreMenu directly
|
||||||
@@ -2405,6 +2408,63 @@ const exportSelectedReceipts = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* US-306: Export all currently visible receipts to Excel.
|
||||||
|
* Exports all receipts from the current filtered list (excludes job items).
|
||||||
|
*/
|
||||||
|
const exportAllReceipts = () => {
|
||||||
|
// Filter out job items - only export actual receipts
|
||||||
|
const receiptsList = unifiedItems.value.filter(item => !isJobItem(item))
|
||||||
|
|
||||||
|
if (receiptsList.length === 0) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'warn',
|
||||||
|
summary: 'Atenție',
|
||||||
|
detail: 'Nu există bonuri de exportat',
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map receipt data to export format
|
||||||
|
const exportData = receiptsList.map(r => ({
|
||||||
|
'Magazin': r.store_name || r.partner_name || '-',
|
||||||
|
'CUI': r.cui || '-',
|
||||||
|
'Data': r.receipt_date ? formatDate(r.receipt_date) : '-',
|
||||||
|
'Nr. Bon': r.receipt_number || '-',
|
||||||
|
'Tip Document': r.receipt_type === 'receipt' ? 'Bon' : r.receipt_type === 'invoice' ? 'Factură' : r.receipt_type || '-',
|
||||||
|
'Direcție': r.direction === 'expense' ? 'Cheltuială' : r.direction === 'income' ? 'Venit' : r.direction || '-',
|
||||||
|
'Suma': r.amount || 0,
|
||||||
|
'TVA': r.tva_total || 0,
|
||||||
|
'Metodă Plată': getPaymentMethodLabel(r) || '-',
|
||||||
|
'Status': getStatusLabel(r.status) || '-',
|
||||||
|
'Creat de': r.created_by || '-',
|
||||||
|
'Creat la': r.created_at ? formatDate(r.created_at) : '-',
|
||||||
|
}))
|
||||||
|
|
||||||
|
const result = exportToExcel(
|
||||||
|
exportData,
|
||||||
|
`bonuri_export_${receiptsList.length}`,
|
||||||
|
'Bonuri'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Export reușit',
|
||||||
|
detail: `${receiptsList.length} bonuri exportate cu succes`,
|
||||||
|
life: 3000,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Eroare',
|
||||||
|
detail: 'Nu s-a putut exporta lista de bonuri',
|
||||||
|
life: 5000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* US-026: Confirmation dialog for bulk delete.
|
* US-026: Confirmation dialog for bulk delete.
|
||||||
* Uses PrimeVue ConfirmDialog to ask user before deleting multiple receipts.
|
* Uses PrimeVue ConfirmDialog to ask user before deleting multiple receipts.
|
||||||
|
|||||||
@@ -977,6 +977,17 @@ onMounted(() => {
|
|||||||
loadMaturityData();
|
loadMaturityData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// US-306: Expose methods for parent component (MobileTopBar actions)
|
||||||
|
defineExpose({
|
||||||
|
exportExcel,
|
||||||
|
exportPDF,
|
||||||
|
selectedPeriod,
|
||||||
|
selectedType,
|
||||||
|
searchTerm,
|
||||||
|
handleSearch,
|
||||||
|
loadDetailedData
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,12 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Mobile Top Bar -->
|
<!-- Mobile Top Bar - US-306: Added Export and Filter actions -->
|
||||||
<MobileTopBar
|
<MobileTopBar
|
||||||
v-if="isMobile"
|
v-if="isMobile"
|
||||||
title="Scadențe"
|
title="Scadențe"
|
||||||
:show-back="true"
|
:show-back="true"
|
||||||
|
:actions="mobileTopBarActions"
|
||||||
@back-click="goBack"
|
@back-click="goBack"
|
||||||
|
@action-click="handleTopBarAction"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 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 -->
|
<!-- US-305: Mobile Tabs for Clienți/Furnizori -->
|
||||||
<div v-if="isMobile" class="mobile-tabs-container">
|
<div v-if="isMobile" class="mobile-tabs-container">
|
||||||
<div class="mobile-tabs">
|
<div class="mobile-tabs">
|
||||||
@@ -53,6 +102,7 @@
|
|||||||
<!-- Main content - MaturityAndDetailsCard -->
|
<!-- Main content - MaturityAndDetailsCard -->
|
||||||
<div v-else class="maturity-container">
|
<div v-else class="maturity-container">
|
||||||
<MaturityAndDetailsCard
|
<MaturityAndDetailsCard
|
||||||
|
ref="maturityCardRef"
|
||||||
:companyId="companyStore.selectedCompany?.id_firma"
|
:companyId="companyStore.selectedCompany?.id_firma"
|
||||||
:activeTab="activeTab"
|
:activeTab="activeTab"
|
||||||
:isMobile="isMobile"
|
:isMobile="isMobile"
|
||||||
@@ -70,8 +120,10 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
|
import Dropdown from 'primevue/dropdown'
|
||||||
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'
|
||||||
|
import BottomSheet from '@shared/components/mobile/BottomSheet.vue'
|
||||||
import MaturityAndDetailsCard from '@reports/components/dashboard/cards/MaturityAndDetailsCard.vue'
|
import MaturityAndDetailsCard from '@reports/components/dashboard/cards/MaturityAndDetailsCard.vue'
|
||||||
import { useCompanyStore } from '@reports/stores/sharedStores'
|
import { useCompanyStore } from '@reports/stores/sharedStores'
|
||||||
|
|
||||||
@@ -86,9 +138,90 @@ const activeTab = ref(route.query.tab === 'suppliers' ? 'suppliers' : 'clients')
|
|||||||
const windowWidth = ref(window.innerWidth)
|
const windowWidth = ref(window.innerWidth)
|
||||||
const isMobile = computed(() => windowWidth.value < 768)
|
const isMobile = computed(() => windowWidth.value < 768)
|
||||||
|
|
||||||
|
// US-306: Ref to MaturityAndDetailsCard for calling exposed methods
|
||||||
|
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-306: Mobile TopBar actions (Export + Filter)
|
||||||
|
const mobileTopBarActions = computed(() => [
|
||||||
|
{
|
||||||
|
icon: 'pi pi-filter',
|
||||||
|
label: 'Filtre',
|
||||||
|
tooltip: 'Filtre',
|
||||||
|
active: hasActiveFilters.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'pi pi-download',
|
||||||
|
label: 'Export',
|
||||||
|
tooltip: 'Export Excel'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// US-306: Handle top bar action clicks
|
||||||
|
const handleTopBarAction = (action) => {
|
||||||
|
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-download') {
|
||||||
|
// Trigger export from child component
|
||||||
|
if (maturityCardRef.value) {
|
||||||
|
maturityCardRef.value.exportExcel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Handle window resize for mobile detection
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
windowWidth.value = window.innerWidth
|
windowWidth.value = window.innerWidth
|
||||||
|
if (!isMobile.value) {
|
||||||
|
showFilters.value = false // Reset when switching to desktop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
@@ -270,4 +403,30 @@ onUnmounted(() => {
|
|||||||
.empty-action {
|
.empty-action {
|
||||||
margin-top: var(--space-md);
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user