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