feat(unified-mobile-desktop-ui): Complete US-510 - Detailed Invoices - Eliminare Filtru Clienți/Furnizori

Implemented by Ralph autonomous loop.
Iteration: 10

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 22:55:24 +00:00
parent 1ad2c25933
commit 2f6aa6a37a
4 changed files with 68 additions and 71 deletions

View File

@@ -221,8 +221,8 @@
"npm run build passes", "npm run build passes",
"Verify in browser: filtrul Clienți/Furnizori nu mai apare" "Verify in browser: filtrul Clienți/Furnizori nu mai apare"
], ],
"passes": false, "passes": true,
"notes": "" "notes": "Completed in iteration 10"
}, },
{ {
"id": "US-511", "id": "US-511",

View File

@@ -124,3 +124,9 @@ Design Reference: src/modules/reports/views/InvoicesView.vue
[2026-01-12 22:45:27] Working on story: US-509 [2026-01-12 22:45:27] Working on story: US-509
[2026-01-12 22:45:27] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_9_US-509.log) [2026-01-12 22:45:27] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_9_US-509.log)
[2026-01-12 22:48:38] SUCCESS: Story US-509 passed! [2026-01-12 22:48:38] SUCCESS: Story US-509 passed!
[2026-01-12 22:48:38] Changes committed
[2026-01-12 22:48:38] Progress: 9/19 stories completed
[2026-01-12 22:48:40] === Iteration 10/100 ===
[2026-01-12 22:48:40] Working on story: US-510
[2026-01-12 22:48:40] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_10_US-510.log)
[2026-01-12 22:55:24] SUCCESS: Story US-510 passed!

View File

@@ -1,8 +1,9 @@
<template> <template>
<!-- US-501: Mobile Top Bar --> <!-- US-501: Mobile Top Bar -->
<!-- US-510: Dynamic title based on route type -->
<MobileTopBar <MobileTopBar
v-if="isMobile" v-if="isMobile"
title="Facturi Detaliate" :title="pageTitle"
:show-back="true" :show-back="true"
:actions="topBarActions" :actions="topBarActions"
@back-click="goBack" @back-click="goBack"
@@ -15,9 +16,10 @@
<main class="main-content" :class="{ 'mobile-layout': isMobile }"> <main class="main-content" :class="{ 'mobile-layout': isMobile }">
<div class="app-container"> <div class="app-container">
<!-- Page Header - only on desktop --> <!-- Page Header - only on desktop -->
<!-- US-510: Dynamic title based on route type -->
<div v-if="!isMobile" class="page-header"> <div v-if="!isMobile" class="page-header">
<h1 class="page-title">Facturi Detaliate</h1> <h1 class="page-title">{{ pageTitle }}</h1>
<p class="page-subtitle">Vizualizare detaliată a facturilor clienți și furnizori</p> <p class="page-subtitle">{{ pageSubtitle }}</p>
</div> </div>
<!-- Loading state when no company selected --> <!-- Loading state when no company selected -->
@@ -38,20 +40,9 @@
<!-- Main content --> <!-- Main content -->
<div v-else class="invoices-container"> <div v-else class="invoices-container">
<!-- Desktop Filters Card --> <!-- Desktop Filters Card -->
<!-- US-510: Removed type dropdown - type is determined by route -->
<div v-if="!isMobile" class="filters-card"> <div v-if="!isMobile" class="filters-card">
<div class="filters-row"> <div class="filters-row">
<div class="filter-group">
<label class="form-label">Tip</label>
<Dropdown
v-model="selectedType"
:options="typeOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați tipul"
class="w-full"
@change="loadDetailedData"
/>
</div>
<div class="filter-group"> <div class="filter-group">
<label class="form-label">Căutare</label> <label class="form-label">Căutare</label>
<InputText <InputText
@@ -101,11 +92,8 @@
</div> </div>
<!-- Mobile Filter Chips (compact summary) --> <!-- Mobile Filter Chips (compact summary) -->
<!-- US-510: Removed type filter chip - type is determined by route -->
<div v-if="isMobile" class="mobile-filter-summary"> <div v-if="isMobile" class="mobile-filter-summary">
<div class="filter-chip" @click="openFilters">
<i class="pi pi-list"></i>
<span>{{ getTypeLabel(selectedType) }}</span>
</div>
<div v-if="searchTerm" class="filter-chip active" @click="openFilters"> <div v-if="searchTerm" class="filter-chip active" @click="openFilters">
<i class="pi pi-search"></i> <i class="pi pi-search"></i>
<span>{{ searchTerm }}</span> <span>{{ searchTerm }}</span>
@@ -136,7 +124,7 @@
<div v-if="!isMobile" class="table-wrapper"> <div v-if="!isMobile" class="table-wrapper">
<!-- Treasury DataTable (no expansion needed) --> <!-- Treasury DataTable (no expansion needed) -->
<DataTable <DataTable
v-if="selectedType === 'treasury'" v-if="invoiceType === 'treasury'"
:value="paginatedData" :value="paginatedData"
:loading="isLoading" :loading="isLoading"
stripedRows stripedRows
@@ -158,12 +146,12 @@
<!-- Table Header --> <!-- Table Header -->
<div class="groups-table-header"> <div class="groups-table-header">
<div class="header-cell expand-col"></div> <div class="header-cell expand-col"></div>
<div class="header-cell name-col">{{ selectedType === 'clients' ? 'Client' : 'Furnizor' }}</div> <div class="header-cell name-col">{{ invoiceType === 'clients' ? 'Client' : 'Furnizor' }}</div>
<div class="header-cell">Nr. Document</div> <div class="header-cell">Nr. Document</div>
<div class="header-cell">Data Document</div> <div class="header-cell">Data Document</div>
<div class="header-cell">Data Scadență</div> <div class="header-cell">Data Scadență</div>
<div class="header-cell text-right">Facturat</div> <div class="header-cell text-right">Facturat</div>
<div class="header-cell text-right">{{ selectedType === 'clients' ? 'Încasat' : 'Achitat' }}</div> <div class="header-cell text-right">{{ invoiceType === 'clients' ? 'Încasat' : 'Achitat' }}</div>
<div class="header-cell text-right">Sold</div> <div class="header-cell text-right">Sold</div>
</div> </div>
@@ -209,7 +197,7 @@
<div class="row-cell text-right"> <div class="row-cell text-right">
<span class="font-mono"> <span class="font-mono">
{{ group.facturi.length === 1 {{ group.facturi.length === 1
? formatCurrency(group.facturi[0][selectedType === 'clients' ? 'incasat' : 'achitat']) ? formatCurrency(group.facturi[0][invoiceType === 'clients' ? 'incasat' : 'achitat'])
: '-' }} : '-' }}
</span> </span>
</div> </div>
@@ -247,7 +235,7 @@
</div> </div>
<div class="row-cell text-right"> <div class="row-cell text-right">
<span class="font-mono"> <span class="font-mono">
{{ formatCurrency(factura[selectedType === 'clients' ? 'incasat' : 'achitat']) }} {{ formatCurrency(factura[invoiceType === 'clients' ? 'incasat' : 'achitat']) }}
</span> </span>
</div> </div>
<div class="row-cell text-right"> <div class="row-cell text-right">
@@ -275,7 +263,7 @@
<!-- Mobile Cards --> <!-- Mobile Cards -->
<div v-if="isMobile" class="mobile-cards"> <div v-if="isMobile" class="mobile-cards">
<!-- Treasury Cards --> <!-- Treasury Cards -->
<template v-if="selectedType === 'treasury'"> <template v-if="invoiceType === 'treasury'">
<div <div
v-for="row in paginatedData" v-for="row in paginatedData"
:key="row.id" :key="row.id"
@@ -375,7 +363,7 @@
</template> </template>
<!-- Empty state --> <!-- Empty state -->
<div v-if="(selectedType === 'treasury' ? paginatedData : paginatedGroups).length === 0" class="empty-data"> <div v-if="(invoiceType === 'treasury' ? paginatedData : paginatedGroups).length === 0" class="empty-data">
<i class="pi pi-inbox"></i> <i class="pi pi-inbox"></i>
<p>Nu există facturi pentru criteriile selectate</p> <p>Nu există facturi pentru criteriile selectate</p>
</div> </div>
@@ -398,7 +386,7 @@
<span class="total-label">Total Sold:</span> <span class="total-label">Total Sold:</span>
<span class="total-value">{{ formatCurrency(calculateTotal('sold')) }}</span> <span class="total-value">{{ formatCurrency(calculateTotal('sold')) }}</span>
</div> </div>
<div v-if="selectedType !== 'treasury'" class="total-item"> <div v-if="invoiceType !== 'treasury'" class="total-item">
<span class="total-label">Total Facturat:</span> <span class="total-label">Total Facturat:</span>
<span class="total-value">{{ formatCurrency(calculateTotal('facturat')) }}</span> <span class="total-value">{{ formatCurrency(calculateTotal('facturat')) }}</span>
</div> </div>
@@ -412,22 +400,11 @@
<MobileBottomNav v-if="isMobile" /> <MobileBottomNav v-if="isMobile" />
<!-- Mobile Filters BottomSheet --> <!-- Mobile Filters BottomSheet -->
<!-- US-510: Removed type dropdown - type is determined by route -->
<BottomSheet v-model="isFilterSheetOpen"> <BottomSheet v-model="isFilterSheetOpen">
<div class="filter-sheet-content"> <div class="filter-sheet-content">
<h3 class="filter-sheet-title">Filtre</h3> <h3 class="filter-sheet-title">Filtre</h3>
<div class="filter-sheet-group">
<label class="form-label">Tip</label>
<Dropdown
v-model="selectedType"
:options="typeOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați tipul"
class="w-full"
/>
</div>
<div class="filter-sheet-group"> <div class="filter-sheet-group">
<label class="form-label">Căutare</label> <label class="form-label">Căutare</label>
<InputText <InputText
@@ -471,7 +448,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted, watch, Transition } from 'vue' import { ref, computed, onMounted, onUnmounted, watch, Transition } from 'vue'
import { useRouter } 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 Dropdown from 'primevue/dropdown'
import InputText from 'primevue/inputtext' import InputText from 'primevue/inputtext'
@@ -491,11 +468,26 @@ import jsPDF from 'jspdf'
import 'jspdf-autotable' import 'jspdf-autotable'
const router = useRouter() const router = useRouter()
const route = useRoute()
const toast = useToast() const toast = useToast()
const dashboardStore = useDashboardStore() const dashboardStore = useDashboardStore()
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
const periodStore = useAccountingPeriodStore() const periodStore = useAccountingPeriodStore()
// US-510: Get invoice type from route meta (clients or suppliers)
const invoiceType = computed(() => route.meta.invoiceType || 'clients')
// US-510: Dynamic page title and subtitle based on invoice type
const pageTitle = computed(() => {
return invoiceType.value === 'clients' ? 'Facturi Clienți' : 'Facturi Furnizori'
})
const pageSubtitle = computed(() => {
return invoiceType.value === 'clients'
? 'Vizualizare detaliată a facturilor clienți'
: 'Vizualizare detaliată a facturilor furnizori'
})
// Mobile detection // Mobile detection
const windowWidth = ref(window.innerWidth) const windowWidth = ref(window.innerWidth)
const isMobile = computed(() => windowWidth.value < 768) const isMobile = computed(() => windowWidth.value < 768)
@@ -507,7 +499,7 @@ const handleResize = () => {
// State // State
const isLoading = ref(false) const isLoading = ref(false)
const error = ref(null) const error = ref(null)
const selectedType = ref('clients') // US-510: Removed selectedType - now using invoiceType from route
const searchTerm = ref('') const searchTerm = ref('')
const selectedPeriod = ref('all') const selectedPeriod = ref('all')
const detailedData = ref([]) const detailedData = ref([])
@@ -517,11 +509,7 @@ const expandedGroups = ref(new Set())
const isFilterSheetOpen = ref(false) const isFilterSheetOpen = ref(false)
// Options // Options
const typeOptions = [ // US-510: Removed typeOptions - type is now determined by route
{ label: 'Clienți', value: 'clients' },
{ label: 'Furnizori', value: 'suppliers' },
{ label: 'Trezorerie', value: 'treasury' }
]
const periodOptions = [ const periodOptions = [
{ label: 'Toate', value: 'all' }, { label: 'Toate', value: 'all' },
@@ -533,10 +521,10 @@ const periodOptions = [
] ]
// US-501: Check if filters have non-default values // US-501: Check if filters have non-default values
// US-510: Removed selectedType check - type is now route-based
const hasActiveFilters = computed(() => { const hasActiveFilters = computed(() => {
return searchTerm.value !== '' || return searchTerm.value !== '' ||
selectedPeriod.value !== 'all' || selectedPeriod.value !== 'all'
selectedType.value !== 'clients'
}) })
// US-501: Mobile TopBar actions (filter, reset, export dropdown) // US-501: Mobile TopBar actions (filter, reset, export dropdown)
@@ -625,10 +613,10 @@ const filteredData = computed(() => {
}) })
const groupedData = computed(() => { const groupedData = computed(() => {
if (selectedType.value === 'treasury') return [] if (invoiceType.value === 'treasury') return []
const groups = {} const groups = {}
const nameField = selectedType.value === 'clients' ? 'client' : 'furnizor' const nameField = invoiceType.value === 'clients' ? 'client' : 'furnizor'
filteredData.value.forEach((row) => { filteredData.value.forEach((row) => {
const name = row[nameField] const name = row[nameField]
@@ -654,12 +642,12 @@ const groupedData = computed(() => {
}) })
const paginatedGroups = computed(() => { const paginatedGroups = computed(() => {
if (selectedType.value === 'treasury') return [] if (invoiceType.value === 'treasury') return []
return groupedData.value return groupedData.value
}) })
const paginatedData = computed(() => { const paginatedData = computed(() => {
if (selectedType.value !== 'treasury') return [] if (invoiceType.value !== 'treasury') return []
return filteredData.value return filteredData.value
}) })
@@ -668,10 +656,7 @@ const totalRecords = computed(() => {
}) })
// Helper functions // Helper functions
const getTypeLabel = (type) => { // US-510: Removed getTypeLabel - no longer needed
const option = typeOptions.find(o => o.value === type)
return option?.label || type
}
const getPeriodLabel = (period) => { const getPeriodLabel = (period) => {
const option = periodOptions.find(o => o.value === period) const option = periodOptions.find(o => o.value === period)
@@ -731,7 +716,7 @@ const handleSearch = () => {
} }
const resetFilters = () => { const resetFilters = () => {
selectedType.value = 'clients' // US-510: Removed type reset - type is now determined by route
searchTerm.value = '' searchTerm.value = ''
selectedPeriod.value = 'all' selectedPeriod.value = 'all'
firstRow.value = 0 firstRow.value = 0
@@ -765,7 +750,7 @@ const loadDetailedData = async () => {
const an = periodStore.selectedPeriod?.an || null const an = periodStore.selectedPeriod?.an || null
const response = await dashboardStore.loadDetailedData( const response = await dashboardStore.loadDetailedData(
selectedType.value, invoiceType.value,
companyStore.selectedCompany.id_firma, companyStore.selectedCompany.id_firma,
page, page,
rowsPerPage.value, rowsPerPage.value,
@@ -794,8 +779,8 @@ const loadDetailedData = async () => {
const exportExcel = () => { const exportExcel = () => {
const ws = XLSX.utils.json_to_sheet(filteredData.value) const ws = XLSX.utils.json_to_sheet(filteredData.value)
const wb = XLSX.utils.book_new() const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, selectedType.value) XLSX.utils.book_append_sheet(wb, ws, invoiceType.value)
XLSX.writeFile(wb, `facturi_${selectedType.value}_${new Date().toISOString().split('T')[0]}.xlsx`) XLSX.writeFile(wb, `facturi_${invoiceType.value}_${new Date().toISOString().split('T')[0]}.xlsx`)
toast.add({ toast.add({
severity: 'success', severity: 'success',
@@ -808,18 +793,18 @@ const exportExcel = () => {
const exportPDF = () => { const exportPDF = () => {
const doc = new jsPDF() const doc = new jsPDF()
const columns = selectedType.value === 'treasury' const columns = invoiceType.value === 'treasury'
? ['Cont', 'Nume Cont', 'Sold', 'Valută', 'Tip'] ? ['Cont', 'Nume Cont', 'Sold', 'Valută', 'Tip']
: selectedType.value === 'clients' : invoiceType.value === 'clients'
? ['Client', 'Nr. Document', 'Data Doc.', 'Scadență', 'Facturat', 'Încasat', 'Sold'] ? ['Client', 'Nr. Document', 'Data Doc.', 'Scadență', 'Facturat', 'Încasat', 'Sold']
: ['Furnizor', 'Nr. Document', 'Data Doc.', 'Scadență', 'Facturat', 'Achitat', 'Sold'] : ['Furnizor', 'Nr. Document', 'Data Doc.', 'Scadență', 'Facturat', 'Achitat', 'Sold']
const rows = filteredData.value.map((row) => { const rows = filteredData.value.map((row) => {
if (selectedType.value === 'treasury') { if (invoiceType.value === 'treasury') {
return [row.cont, row.nume_cont, formatCurrency(row.sold), row.valuta, row.tip] return [row.cont, row.nume_cont, formatCurrency(row.sold), row.valuta, row.tip]
} }
const nameField = selectedType.value === 'clients' ? 'client' : 'furnizor' const nameField = invoiceType.value === 'clients' ? 'client' : 'furnizor'
const paidField = selectedType.value === 'clients' ? 'incasat' : 'achitat' const paidField = invoiceType.value === 'clients' ? 'incasat' : 'achitat'
return [ return [
row[nameField], row[nameField],
row.numar_document, row.numar_document,
@@ -839,7 +824,7 @@ const exportPDF = () => {
headStyles: { fillColor: [59, 130, 246] } headStyles: { fillColor: [59, 130, 246] }
}) })
doc.save(`facturi_${selectedType.value}_${new Date().toISOString().split('T')[0]}.pdf`) doc.save(`facturi_${invoiceType.value}_${new Date().toISOString().split('T')[0]}.pdf`)
toast.add({ toast.add({
severity: 'success', severity: 'success',

View File

@@ -61,10 +61,16 @@ const routes = [
meta: { requiresAuth: true, title: 'Analiză Scadențe - ROA2WEB' } meta: { requiresAuth: true, title: 'Analiză Scadențe - ROA2WEB' }
}, },
{ {
path: 'detailed-invoices', path: 'detailed-invoices/clients',
name: 'DetailedInvoices', name: 'DetailedInvoicesClients',
component: () => import('@reports/views/DetailedInvoicesView.vue'), component: () => import('@reports/views/DetailedInvoicesView.vue'),
meta: { requiresAuth: true, title: 'Facturi Detaliate - ROA2WEB' } meta: { requiresAuth: true, title: 'Facturi Clienți - ROA2WEB', invoiceType: 'clients' }
},
{
path: 'detailed-invoices/suppliers',
name: 'DetailedInvoicesSuppliers',
component: () => import('@reports/views/DetailedInvoicesView.vue'),
meta: { requiresAuth: true, title: 'Facturi Furnizori - ROA2WEB', invoiceType: 'suppliers' }
} }
] ]
}, },