fix(reports): neutralize totals color and fix dark mode button contrast

- Remove green/red/blue coloring from summary totals on all report pages
  (Facturi, Facturi pe Parteneri, Casă, Bancă) — mobile and desktop,
  light and dark mode; totals now use --text-color like TrialBalance
- Fix SplitButton (Export) dark mode: was blue (--color-primary), now
  matches secondary outlined style (--text-color / --surface-border)
- Update button syntax: severity="secondary" outlined instead of legacy
  class="p-button-outlined p-button-secondary"
- DetailedInvoices: partner-meta (8 facturi) inline with partner name
  via flex-direction row instead of column
- TrialBalance: mobile flat-row list layout replacing card layout
- Dashboard mobile title: shows company name + period instead of static "Dashboard"
- Navigation: move Facturi pe Parteneri to RAPOARTE section;
  update MobileBottomNav default items (Detalii replaces Facturi)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-26 08:40:59 +00:00
parent 1839285ac3
commit 0459b3feaf
10 changed files with 654 additions and 646 deletions

View File

@@ -184,6 +184,11 @@
cursor: pointer;
}
/* DataTable body text - override saga-blue hardcoded #495057 */
.p-datatable .p-datatable-tbody > tr {
color: var(--text-color) !important;
}
/* Compact DataTable variant (p-datatable-sm) */
.p-datatable-sm .p-datatable-thead > tr > th {
padding: 0.5rem 0.75rem !important;
@@ -683,9 +688,121 @@
}
}
/* ===== Menu (popup/SplitButton dropdown) ===== */
.p-menu {
background: var(--surface-card);
border-color: var(--surface-border);
color: var(--text-color);
}
.p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link {
color: var(--text-color);
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
}
.p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link .p-menuitem-text {
color: var(--text-color);
}
.p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link .p-menuitem-icon {
color: var(--text-color-secondary);
margin-right: 0;
}
.p-menu .p-menuitem > .p-menuitem-content:hover,
.p-menu .p-menuitem > .p-menuitem-content:not(.p-highlight):not(.p-disabled).p-focus {
background: var(--surface-hover);
}
[data-theme="dark"] .p-menu {
background: var(--surface-card);
border-color: var(--surface-border);
}
[data-theme="dark"] .p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link {
color: var(--text-color);
}
[data-theme="dark"] .p-menu .p-menuitem > .p-menuitem-content:hover {
background: var(--surface-hover);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .p-menu {
background: var(--surface-card);
border-color: var(--surface-border);
}
:root:not([data-theme]) .p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link {
color: var(--text-color);
}
:root:not([data-theme]) .p-menu .p-menuitem > .p-menuitem-content:hover {
background: var(--surface-hover);
}
}
/* Server dropdown in login form uses default styling (inherits from global rules above) */
/* Server dropdown in header is styled in header.css to match CompanySelector */
/* ===== Dark Mode Outlined Button Contrast Fix ===== */
/* PrimeVue saga-blue (light) theme doesn't adapt outlined buttons for dark mode.
Secondary outlined buttons become invisible (grey on dark bg).
SplitButton outlined also needs explicit dark-mode color. */
[data-theme="dark"] .p-button.p-button-outlined.p-button-secondary {
color: var(--text-color) !important;
border-color: var(--surface-border) !important;
background: transparent !important;
}
[data-theme="dark"] .p-button.p-button-outlined.p-button-secondary:hover {
background: var(--surface-hover) !important;
color: var(--text-color) !important;
border-color: var(--text-color-secondary) !important;
}
[data-theme="dark"] .p-splitbutton .p-splitbutton-defaultbutton.p-button-outlined,
[data-theme="dark"] .p-splitbutton .p-splitbutton-menubutton.p-button-outlined {
color: var(--text-color) !important;
border-color: var(--surface-border) !important;
background: transparent !important;
}
[data-theme="dark"] .p-splitbutton .p-splitbutton-defaultbutton.p-button-outlined:hover,
[data-theme="dark"] .p-splitbutton .p-splitbutton-menubutton.p-button-outlined:hover {
background: var(--surface-hover) !important;
color: var(--text-color) !important;
border-color: var(--text-color-secondary) !important;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .p-button.p-button-outlined.p-button-secondary {
color: var(--text-color) !important;
border-color: var(--surface-border) !important;
background: transparent !important;
}
:root:not([data-theme]) .p-button.p-button-outlined.p-button-secondary:hover {
background: var(--surface-hover) !important;
color: var(--text-color) !important;
border-color: var(--text-color-secondary) !important;
}
:root:not([data-theme]) .p-splitbutton .p-splitbutton-defaultbutton.p-button-outlined,
:root:not([data-theme]) .p-splitbutton .p-splitbutton-menubutton.p-button-outlined {
color: var(--text-color) !important;
border-color: var(--surface-border) !important;
background: transparent !important;
}
:root:not([data-theme]) .p-splitbutton .p-splitbutton-defaultbutton.p-button-outlined:hover,
:root:not([data-theme]) .p-splitbutton .p-splitbutton-menubutton.p-button-outlined:hover {
background: var(--surface-hover) !important;
color: var(--text-color) !important;
border-color: var(--text-color-secondary) !important;
}
}
/* ===== All Dialogs - Dark Mode ===== */
/* Dialog background + content folosesc design tokens */

View File

@@ -4,6 +4,7 @@ export const menuSections = [
items: [
{ to: '/reports/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi' },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi pe Parteneri' },
{ to: '/reports/cash', icon: 'pi pi-wallet', label: 'Casă' },
{ to: '/reports/bank', icon: 'pi pi-building', label: 'Bancă' },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță de Verificare' }
@@ -12,8 +13,7 @@ export const menuSections = [
{
title: 'Analize',
items: [
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe' },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate' }
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe' }
]
},
{

View File

@@ -74,7 +74,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="resetFilters(); showFilters = false"
/>
<Button
@@ -124,11 +125,11 @@
</div>
<div class="total-item">
<span class="total-label">Încasări:</span>
<span class="total-value incasari">{{ formatCompact(treasuryStore.totals.total_incasari_all) }}</span>
<span class="total-value">{{ formatCompact(treasuryStore.totals.total_incasari_all) }}</span>
</div>
<div class="total-item">
<span class="total-label">Plăți:</span>
<span class="total-value plati">{{ formatCompact(treasuryStore.totals.total_plati_all) }}</span>
<span class="total-value">{{ formatCompact(treasuryStore.totals.total_plati_all) }}</span>
</div>
<div class="total-item">
<span class="total-label">Sold Final:</span>
@@ -190,7 +191,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="resetFilters"
/>
<SplitButton
@@ -198,7 +200,7 @@
icon="pi pi-download"
:model="desktopExportItems"
@click="exportPDF"
class="p-button-outlined"
outlined
:disabled="!hasData"
/>
<Button
@@ -216,55 +218,49 @@
<div v-if="!isMobile && companyStore.selectedCompany" class="summary-stats-inline">
<div class="stat-item">
<span class="stat-label">Sold Precedent:</span>
<span
class="stat-value"
:class="treasuryStore.totals.sold_precedent_all >= 0 ? 'incasari' : 'plati'"
>{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}</span
>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Încasări:</span>
<span class="stat-value incasari">{{
formatCurrency(treasuryStore.totals.total_incasari_all)
}}</span>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.total_incasari_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Plăți:</span>
<span class="stat-value plati">{{
formatCurrency(treasuryStore.totals.total_plati_all)
}}</span>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.total_plati_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Sold Final:</span>
<span
class="stat-value"
:class="treasuryStore.totals.sold_final_all >= 0 ? 'incasari' : 'plati'"
>{{ formatCurrency(treasuryStore.totals.sold_final_all) }}</span
>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.sold_final_all) }}</span>
</div>
</div>
<!-- Data Table -->
<Card v-if="companyStore.selectedCompany" class="data-card">
<template #content>
<!-- Mobile: Card Layout -->
<!-- Mobile: Compact Rows with Collapsible Meta -->
<div v-if="isMobile" class="mobile-card-list">
<div
v-for="reg in treasuryStore.registers"
v-for="(reg, index) in treasuryStore.registers"
:key="`${reg.dataact}-${reg.nract}`"
class="mobile-data-card"
class="mobile-compact-row"
>
<div class="card-header">{{ reg.nume || 'Fără partener' }}</div>
<div class="card-row">
<span class="card-meta">{{ formatDateShort(reg.dataact) }} · {{ reg.nume_cont_bancar }}</span>
<div class="compact-main" @click="toggleMeta(index)">
<span class="compact-date">{{ formatDateShort(reg.dataact) }}</span>
<span class="compact-partner">{{ reg.nume || 'Fără partener' }}</span>
<span
class="card-amount"
class="compact-amount"
:class="reg.incasari > 0 ? 'positive' : (reg.plati > 0 ? 'negative' : '')"
>
<template v-if="reg.incasari > 0">+{{ formatNumber(reg.incasari) }}</template>
<template v-else-if="reg.plati > 0">-{{ formatNumber(reg.plati) }}</template>
<template v-else>{{ formatNumber(0) }}</template>
<template v-else>0</template>
</span>
<i :class="expandedMeta.has(index) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="meta-chevron"></i>
</div>
<div v-if="expandedMeta.has(index)" class="compact-meta">
<span v-if="reg.nume_cont_bancar" class="meta-item">{{ reg.nume_cont_bancar }}</span>
<span v-if="reg.nract" class="meta-item">Nr: {{ reg.nract }}</span>
<span v-if="reg.explicatia" class="meta-item">{{ truncateText(reg.explicatia, 80) }}</span>
</div>
</div>
<div v-if="treasuryStore.registers.length === 0" class="mobile-empty">
@@ -419,6 +415,14 @@ const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
const isMobile = ref(window.innerWidth < 768);
const showFilters = ref(false);
const showDrawer = ref(false);
const expandedMeta = ref(new Set());
const toggleMeta = (index) => {
const s = new Set(expandedMeta.value);
if (s.has(index)) s.delete(index);
else s.add(index);
expandedMeta.value = s;
};
// Handle logout from drawer menu
const handleLogout = async () => {
@@ -527,11 +531,11 @@ const pagination = ref({
rows: 50,
});
const formatCurrency = (amount, currency = "RON") => {
if (!amount) return "0,00 " + currency;
const formatCurrency = (amount) => {
if (!amount || amount === 0) return "0,00";
return new Intl.NumberFormat("ro-RO", {
style: "currency",
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
};
@@ -943,6 +947,12 @@ watch(
},
{ immediate: true },
);
// Reset expanded meta when data changes
watch(
() => treasuryStore.registers,
() => { expandedMeta.value = new Set(); }
);
</script>
<style scoped>
@@ -1025,63 +1035,83 @@ watch(
color: var(--text-color);
}
.mobile-totals-bar .total-value.incasari {
color: var(--green-600);
}
.mobile-totals-bar .total-value.plati {
color: var(--red-600);
}
/* Colors removed - totals use neutral --text-color like TrialBalance */
/* ================================================
Mobile Card List
Mobile Compact Rows with Collapsible Meta
================================================ */
.mobile-card-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-data-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
padding: var(--space-md);
.mobile-compact-row {
border-bottom: 1px solid var(--surface-border);
}
.mobile-data-card .card-header {
font-weight: var(--font-semibold);
color: var(--text-color);
margin-bottom: var(--space-xs);
font-size: var(--text-base);
}
.mobile-data-card .card-row {
.compact-main {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
color: var(--text-color-secondary);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
cursor: pointer;
}
.mobile-data-card .card-meta {
.compact-main:active {
background: var(--surface-hover);
}
.compact-date {
font-size: var(--text-xs);
color: var(--text-color-secondary);
min-width: 40px;
flex-shrink: 0;
}
.compact-partner {
flex: 1;
font-size: var(--text-sm);
color: var(--text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.compact-amount {
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
flex-shrink: 0;
}
.compact-amount.positive {
color: var(--green-600);
}
.compact-amount.negative {
color: var(--red-600);
}
.meta-chevron {
font-size: var(--text-xs);
color: var(--text-color-secondary);
flex-shrink: 0;
}
.compact-meta {
display: flex;
flex-direction: column;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md) var(--space-sm) calc(40px + var(--space-md) + var(--space-sm));
background: var(--surface-hover);
font-size: var(--text-xs);
color: var(--text-color-secondary);
}
.mobile-data-card .card-amount {
font-weight: var(--font-semibold);
color: var(--text-color);
font-variant-numeric: tabular-nums;
}
.mobile-data-card .card-amount.positive {
color: var(--green-600);
}
.mobile-data-card .card-amount.negative {
color: var(--red-600);
.meta-item {
display: block;
}
.mobile-empty {
@@ -1126,41 +1156,33 @@ watch(
Dark Mode Support
================================================ */
[data-theme="dark"] .mobile-totals-bar .total-value.incasari {
[data-theme="dark"] .compact-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-totals-bar .total-value.plati {
color: var(--red-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.negative {
[data-theme="dark"] .compact-amount.negative {
color: var(--red-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-totals-bar .total-value.incasari {
:root:not([data-theme]) .compact-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-totals-bar .total-value.plati {
:root:not([data-theme]) .compact-amount.negative {
color: var(--red-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.negative {
:root:not([data-theme]) .numeric-value.negative {
color: var(--red-400);
}
}
[data-theme="dark"] .numeric-value.negative {
color: var(--red-400);
}
/* ================================================
Responsive Design
================================================ */

View File

@@ -74,7 +74,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="resetFilters(); showFilters = false"
/>
<Button
@@ -124,11 +125,11 @@
</div>
<div class="total-item">
<span class="total-label">Încasări:</span>
<span class="total-value incasari">{{ formatCompact(treasuryStore.totals.total_incasari_all) }}</span>
<span class="total-value">{{ formatCompact(treasuryStore.totals.total_incasari_all) }}</span>
</div>
<div class="total-item">
<span class="total-label">Plăți:</span>
<span class="total-value plati">{{ formatCompact(treasuryStore.totals.total_plati_all) }}</span>
<span class="total-value">{{ formatCompact(treasuryStore.totals.total_plati_all) }}</span>
</div>
<div class="total-item">
<span class="total-label">Sold Final:</span>
@@ -190,7 +191,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="resetFilters"
/>
<SplitButton
@@ -198,7 +200,7 @@
icon="pi pi-download"
:model="desktopExportItems"
@click="exportPDF"
class="p-button-outlined"
outlined
:disabled="!hasData"
/>
<Button
@@ -216,55 +218,49 @@
<div v-if="!isMobile && companyStore.selectedCompany" class="summary-stats-inline">
<div class="stat-item">
<span class="stat-label">Sold Precedent:</span>
<span
class="stat-value"
:class="treasuryStore.totals.sold_precedent_all >= 0 ? 'incasari' : 'plati'"
>{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}</span
>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Încasări:</span>
<span class="stat-value incasari">{{
formatCurrency(treasuryStore.totals.total_incasari_all)
}}</span>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.total_incasari_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Plăți:</span>
<span class="stat-value plati">{{
formatCurrency(treasuryStore.totals.total_plati_all)
}}</span>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.total_plati_all) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Sold Final:</span>
<span
class="stat-value"
:class="treasuryStore.totals.sold_final_all >= 0 ? 'incasari' : 'plati'"
>{{ formatCurrency(treasuryStore.totals.sold_final_all) }}</span
>
<span class="stat-value">{{ formatCurrency(treasuryStore.totals.sold_final_all) }}</span>
</div>
</div>
<!-- Data Table -->
<Card v-if="companyStore.selectedCompany" class="data-card">
<template #content>
<!-- Mobile: Card Layout -->
<!-- Mobile: Compact Rows with Collapsible Meta -->
<div v-if="isMobile" class="mobile-card-list">
<div
v-for="reg in treasuryStore.registers"
v-for="(reg, index) in treasuryStore.registers"
:key="`${reg.dataact}-${reg.nract}`"
class="mobile-data-card"
class="mobile-compact-row"
>
<div class="card-header">{{ reg.nume || 'Fără partener' }}</div>
<div class="card-row">
<span class="card-meta">{{ formatDateShort(reg.dataact) }} · {{ reg.nume_cont_bancar }}</span>
<div class="compact-main" @click="toggleMeta(index)">
<span class="compact-date">{{ formatDateShort(reg.dataact) }}</span>
<span class="compact-partner">{{ reg.nume || 'Fără partener' }}</span>
<span
class="card-amount"
class="compact-amount"
:class="reg.incasari > 0 ? 'positive' : (reg.plati > 0 ? 'negative' : '')"
>
<template v-if="reg.incasari > 0">+{{ formatNumber(reg.incasari) }}</template>
<template v-else-if="reg.plati > 0">-{{ formatNumber(reg.plati) }}</template>
<template v-else>{{ formatNumber(0) }}</template>
<template v-else>0</template>
</span>
<i :class="expandedMeta.has(index) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="meta-chevron"></i>
</div>
<div v-if="expandedMeta.has(index)" class="compact-meta">
<span v-if="reg.nume_cont_bancar" class="meta-item">{{ reg.nume_cont_bancar }}</span>
<span v-if="reg.nract" class="meta-item">Nr: {{ reg.nract }}</span>
<span v-if="reg.explicatia" class="meta-item">{{ truncateText(reg.explicatia, 80) }}</span>
</div>
</div>
<div v-if="treasuryStore.registers.length === 0" class="mobile-empty">
@@ -419,6 +415,14 @@ const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
const isMobile = ref(window.innerWidth < 768);
const showFilters = ref(false);
const showDrawer = ref(false);
const expandedMeta = ref(new Set());
const toggleMeta = (index) => {
const s = new Set(expandedMeta.value);
if (s.has(index)) s.delete(index);
else s.add(index);
expandedMeta.value = s;
};
// Handle logout from drawer menu
const handleLogout = async () => {
@@ -527,11 +531,11 @@ const pagination = ref({
rows: 50,
});
const formatCurrency = (amount, currency = "RON") => {
if (!amount) return "0,00 " + currency;
const formatCurrency = (amount) => {
if (!amount || amount === 0) return "0,00";
return new Intl.NumberFormat("ro-RO", {
style: "currency",
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
};
@@ -943,6 +947,12 @@ watch(
},
{ immediate: true },
);
// Reset expanded meta when data changes
watch(
() => treasuryStore.registers,
() => { expandedMeta.value = new Set(); }
);
</script>
<style scoped>
@@ -1025,63 +1035,83 @@ watch(
color: var(--text-color);
}
.mobile-totals-bar .total-value.incasari {
color: var(--green-600);
}
.mobile-totals-bar .total-value.plati {
color: var(--red-600);
}
/* Colors removed - totals use neutral --text-color like TrialBalance */
/* ================================================
Mobile Card List
Mobile Compact Rows with Collapsible Meta
================================================ */
.mobile-card-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-data-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
padding: var(--space-md);
.mobile-compact-row {
border-bottom: 1px solid var(--surface-border);
}
.mobile-data-card .card-header {
font-weight: var(--font-semibold);
color: var(--text-color);
margin-bottom: var(--space-xs);
font-size: var(--text-base);
}
.mobile-data-card .card-row {
.compact-main {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
color: var(--text-color-secondary);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
cursor: pointer;
}
.mobile-data-card .card-meta {
.compact-main:active {
background: var(--surface-hover);
}
.compact-date {
font-size: var(--text-xs);
color: var(--text-color-secondary);
min-width: 40px;
flex-shrink: 0;
}
.compact-partner {
flex: 1;
font-size: var(--text-sm);
color: var(--text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.compact-amount {
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
flex-shrink: 0;
}
.compact-amount.positive {
color: var(--green-600);
}
.compact-amount.negative {
color: var(--red-600);
}
.meta-chevron {
font-size: var(--text-xs);
color: var(--text-color-secondary);
flex-shrink: 0;
}
.compact-meta {
display: flex;
flex-direction: column;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md) var(--space-sm) calc(40px + var(--space-md) + var(--space-sm));
background: var(--surface-hover);
font-size: var(--text-xs);
color: var(--text-color-secondary);
}
.mobile-data-card .card-amount {
font-weight: var(--font-semibold);
color: var(--text-color);
font-variant-numeric: tabular-nums;
}
.mobile-data-card .card-amount.positive {
color: var(--green-600);
}
.mobile-data-card .card-amount.negative {
color: var(--red-600);
.meta-item {
display: block;
}
.mobile-empty {
@@ -1126,41 +1156,33 @@ watch(
Dark Mode Support
================================================ */
[data-theme="dark"] .mobile-totals-bar .total-value.incasari {
[data-theme="dark"] .compact-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-totals-bar .total-value.plati {
color: var(--red-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.negative {
[data-theme="dark"] .compact-amount.negative {
color: var(--red-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-totals-bar .total-value.incasari {
:root:not([data-theme]) .compact-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-totals-bar .total-value.plati {
:root:not([data-theme]) .compact-amount.negative {
color: var(--red-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.negative {
:root:not([data-theme]) .numeric-value.negative {
color: var(--red-400);
}
}
[data-theme="dark"] .numeric-value.negative {
color: var(--red-400);
}
/* ================================================
Responsive Design
================================================ */

View File

@@ -2,7 +2,7 @@
<!-- Mobile Top Bar -->
<MobileTopBar
v-if="isMobile"
title="Dashboard"
:title="dashboardTitle"
:show-menu="true"
:actions="mobileTopBarActions"
@menu-click="showDrawer = true"
@@ -775,6 +775,17 @@ const bancaPreviousSparkline = computed(() => {
const windowWidth = ref(window.innerWidth);
const isMobile = computed(() => windowWidth.value < 768);
const dashboardTitle = computed(() => {
const company = companyStore.selectedCompany?.name;
const period = periodStore.selectedPeriod;
if (!company) return 'Dashboard';
if (period) {
const lunaStr = String(period.luna).padStart(2, '0');
return `${company} · ${lunaStr}/${period.an}`;
}
return company;
});
// Handle window resize for mobile detection
const handleResize = () => {
windowWidth.value = window.innerWidth;

View File

@@ -2,7 +2,7 @@
<!-- US-703: Mobile Top Bar with Hamburger Menu (not back button) -->
<MobileTopBar
v-if="isMobile"
title="Facturi Detaliate"
title="Facturi pe Parteneri"
:show-menu="true"
:actions="topBarActions"
@menu-click="showDrawer = true"
@@ -52,8 +52,8 @@
<!-- Page Header - only on desktop -->
<!-- US-603: Desktop header with tabs -->
<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">Facturi pe Parteneri</h1>
<p class="page-subtitle">Vizualizare facturi grupate pe clienți și furnizori</p>
<!-- US-603: Desktop Tabs for Clienți/Furnizori -->
<div class="desktop-tabs">
@@ -106,18 +106,6 @@
@input="handleSearch"
/>
</div>
<div class="filter-group">
<label class="form-label">Perioadă</label>
<Dropdown
v-model="selectedPeriod"
:options="periodOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați perioada"
class="w-full"
@change="loadDetailedData"
/>
</div>
</div>
<!-- US-501: Desktop Action buttons with Export dropdown -->
<div class="filters-actions">
@@ -152,11 +140,6 @@
<span>{{ searchTerm }}</span>
<i class="pi pi-times" @click="clearSearch"></i>
</div>
<div v-if="selectedPeriod !== 'all'" class="filter-chip active">
<i class="pi pi-calendar"></i>
<span>{{ getPeriodLabel(selectedPeriod) }}</span>
<i class="pi pi-times" @click="clearPeriod"></i>
</div>
</div>
<!-- Loading State -->
@@ -174,6 +157,26 @@
<!-- Data Table / Cards -->
<div v-else class="data-section">
<!-- Desktop Totals - above table -->
<div v-if="!isMobile && filteredData.length > 0" class="summary-stats-inline">
<div class="stat-item">
<span class="stat-label">Total Sold:</span>
<span class="stat-value">{{ formatCurrency(calculateTotal('sold')) }}</span>
</div>
<div v-if="invoiceType !== 'treasury'" class="stat-item">
<span class="stat-label">Total Facturat:</span>
<span class="stat-value">{{ formatCurrency(calculateTotal('facturat')) }}</span>
</div>
</div>
<!-- Mobile Totals - above cards -->
<div v-if="isMobile && filteredData.length > 0" class="mobile-totals-bar">
<div class="mobile-totals-content">
<span class="total-label">Sold Total:</span>
<span class="total-value">{{ formatCurrency(calculateTotal('sold')) }}</span>
</div>
</div>
<!-- Desktop Table -->
<div v-if="!isMobile" class="table-wrapper">
<!-- Treasury DataTable (no expansion needed) -->
@@ -314,104 +317,57 @@
</div>
</div>
<!-- Mobile Cards -->
<div v-if="isMobile" class="mobile-cards">
<!-- Treasury Cards -->
<!-- Mobile: Collapsible Rows -->
<div v-if="isMobile" class="mobile-partner-list">
<!-- Treasury: simple flat rows -->
<template v-if="invoiceType === 'treasury'">
<div
v-for="row in paginatedData"
:key="row.id"
class="invoice-card"
class="mobile-partner-row treasury-row"
>
<div class="card-header-row">
<strong>{{ row.cont }}</strong>
<span class="card-badge">{{ row.valuta }}</span>
</div>
<div class="card-body">
<div class="card-field">
<span class="field-label">Nume Cont</span>
<span class="field-value">{{ row.nume_cont }}</span>
</div>
<div class="card-field highlight">
<span class="field-label">Sold</span>
<span class="field-value sold-value">{{ formatCurrency(row.sold) }}</span>
<div class="partner-header">
<div class="partner-info">
<span class="partner-name">{{ row.cont }}</span>
<span class="partner-meta">{{ row.nume_cont }}</span>
</div>
<span class="partner-sold">{{ formatCurrency(row.sold) }}</span>
</div>
</div>
</template>
<!-- Client/Supplier Cards -->
<!-- Clients/Suppliers: collapsible partner rows -->
<template v-else>
<div
v-for="group in paginatedGroups"
:key="group.name"
class="invoice-card"
:class="{ 'has-multiple': group.facturi.length > 1 }"
@click="group.facturi.length > 1 && toggleGroup(group.name)"
class="mobile-partner-row"
>
<div class="card-header-row">
<div class="header-left">
<strong>{{ group.name }}</strong>
<span v-if="group.facturi.length > 1" class="count-badge">
({{ group.facturi.length }})
</span>
<div class="partner-header" @click="toggleGroup(group.name)">
<div class="partner-info">
<span class="partner-name">{{ group.name }}</span>
<span class="partner-meta">({{ group.facturi.length }})</span>
</div>
<div class="header-right">
<span
class="sold-value"
:class="{ 'sold-restant': group.hasRestant }"
>
<div class="partner-total">
<span class="partner-sold" :class="{ overdue: group.hasRestant }">
{{ formatCurrency(group.totalSold) }}
</span>
<i
v-if="group.facturi.length > 1"
:class="['pi', isGroupExpanded(group.name) ? 'pi-chevron-up' : 'pi-chevron-down']"
></i>
<i :class="isGroupExpanded(group.name) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="partner-chevron"></i>
</div>
</div>
<!-- Single invoice details -->
<div v-if="group.facturi.length === 1" class="card-body">
<div class="card-field">
<span class="field-label">Nr. Document</span>
<span class="field-value">{{ group.facturi[0].numar_document }}</span>
</div>
<div class="card-row-inline">
<div class="card-field">
<span class="field-label">Data Doc.</span>
<span class="field-value">{{ formatDate(group.facturi[0].data_document) }}</span>
</div>
<div class="card-field">
<span class="field-label">Scadență</span>
<span class="field-value">{{ formatDate(group.facturi[0].data_scadenta) }}</span>
</div>
</div>
<div class="card-status" :class="getStatusClass(group.facturi[0].status)">
{{ group.facturi[0].status }}
</div>
</div>
<!-- Multiple invoices expanded -->
<div v-if="group.facturi.length > 1 && isGroupExpanded(group.name)" class="card-sub-items">
<div v-if="isGroupExpanded(group.name)" class="partner-invoices">
<div
v-for="(factura, idx) in group.facturi"
:key="`${group.name}-${idx}`"
class="sub-item"
>
<div class="sub-item-header">
<span>{{ factura.numar_document }}</span>
<span
class="sub-item-sold"
:class="{ 'sold-restant': factura.status === 'Restant' }"
class="invoice-line"
>
<span class="invoice-ref">
{{ factura.numar_document }} · {{ formatDate(factura.data_document) }} · sc:{{ formatDate(factura.data_scadenta) }}
</span>
<span class="invoice-sold" :class="{ overdue: factura.status === 'Restant' }">
{{ formatCurrency(factura.sold) }}
</span>
</div>
<div class="sub-item-dates">
<span>{{ formatDate(factura.data_scadenta) }}</span>
<span :class="getStatusClass(factura.status)">{{ factura.status }}</span>
</div>
</div>
</div>
</div>
</template>
@@ -434,17 +390,6 @@
/>
</div>
<!-- Totals Summary -->
<div v-if="filteredData.length > 0" class="totals-summary">
<div class="total-item">
<span class="total-label">Total Sold:</span>
<span class="total-value">{{ formatCurrency(calculateTotal('sold')) }}</span>
</div>
<div v-if="invoiceType !== 'treasury'" class="total-item">
<span class="total-label">Total Facturat:</span>
<span class="total-value">{{ formatCurrency(calculateTotal('facturat')) }}</span>
</div>
</div>
</div>
</div>
</div>
@@ -468,18 +413,6 @@
/>
</div>
<div class="filter-sheet-group">
<label class="form-label">Perioadă</label>
<Dropdown
v-model="selectedPeriod"
:options="periodOptions"
optionLabel="label"
optionValue="value"
placeholder="Selectați perioada"
class="w-full"
/>
</div>
<div class="filter-sheet-actions">
<Button
label="Resetează"
@@ -504,7 +437,6 @@
import { ref, computed, onMounted, onUnmounted, watch, Transition } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import Button from 'primevue/button'
import Dropdown from 'primevue/dropdown'
import InputText from 'primevue/inputtext'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
@@ -519,8 +451,8 @@ import SplitButton from 'primevue/splitbutton'
import { useDashboardStore } from '@reports/stores/dashboard'
import { useCompanyStore, useAccountingPeriodStore, useAuthStore } from '@reports/stores/sharedStores'
import * as XLSX from 'xlsx'
import jsPDF from 'jspdf'
import 'jspdf-autotable'
import { jsPDF } from 'jspdf'
import autoTable from 'jspdf-autotable'
const router = useRouter()
const route = useRoute()
@@ -572,7 +504,6 @@ const isLoading = ref(false)
const error = ref(null)
// US-510: Removed selectedType - now using invoiceType from route
const searchTerm = ref('')
const selectedPeriod = ref('all')
const detailedData = ref([])
const firstRow = ref(0)
const rowsPerPage = ref(25)
@@ -582,20 +513,10 @@ const isFilterSheetOpen = ref(false)
// Options
// US-510: Removed typeOptions - type is now determined by route
const periodOptions = [
{ label: 'Toate', value: 'all' },
{ 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' }
]
// 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'
return searchTerm.value !== ''
})
// US-501: Mobile TopBar actions (filter, reset, export dropdown)
@@ -729,16 +650,6 @@ const totalRecords = computed(() => {
// Helper functions
// US-510: Removed getTypeLabel - no longer needed
const getPeriodLabel = (period) => {
const option = periodOptions.find(o => o.value === period)
return option?.label || period
}
const getStatusClass = (status) => {
if (status === 'Restant') return 'status-restant'
return 'status-ok'
}
const toggleGroup = (groupName) => {
if (expandedGroups.value.has(groupName)) {
expandedGroups.value.delete(groupName)
@@ -811,12 +722,6 @@ const clearSearch = () => {
handleSearch()
}
// US-511: Clear period filter from chip
const clearPeriod = () => {
selectedPeriod.value = 'all'
loadDetailedData()
}
const handleSearch = () => {
firstRow.value = 0
expandedGroups.value.clear()
@@ -825,7 +730,6 @@ const handleSearch = () => {
const resetFilters = () => {
// US-510: Removed type reset - type is now determined by route
searchTerm.value = ''
selectedPeriod.value = 'all'
firstRow.value = 0
expandedGroups.value.clear()
loadDetailedData()
@@ -923,7 +827,7 @@ const exportPDF = () => {
]
})
doc.autoTable({
autoTable(doc, {
head: [columns],
body: rows,
theme: 'grid',
@@ -1537,154 +1441,125 @@ onUnmounted(() => {
color: var(--color-error) !important;
}
/* Mobile Cards */
.mobile-cards {
display: flex;
flex-direction: column;
gap: var(--space-md);
[data-theme="dark"] .sold-restant {
color: var(--red-400) !important;
}
.invoice-card {
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .sold-restant {
color: var(--red-400) !important;
}
}
/* Mobile Partner List */
.mobile-partner-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-partner-row {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
overflow: hidden;
}
.invoice-card.has-multiple {
.partner-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-sm) var(--space-md);
cursor: pointer;
}
.card-header-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-md);
background: var(--surface-hover);
border-bottom: 1px solid var(--surface-border);
}
.header-left {
display: flex;
align-items: center;
gap: var(--space-sm);
min-height: 44px;
}
.header-right {
.treasury-row .partner-header {
cursor: default;
}
.partner-info {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-sm);
}
.card-badge {
font-size: var(--text-xs);
padding: 2px var(--space-xs);
background: var(--primary-100);
color: var(--color-primary);
border-radius: var(--radius-sm);
}
.card-body {
padding: var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.card-field {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-field.highlight {
padding-top: var(--space-sm);
border-top: 1px solid var(--surface-border);
margin-top: var(--space-xs);
}
.card-row-inline {
display: flex;
gap: var(--space-md);
}
.card-row-inline .card-field {
gap: var(--space-xs);
flex: 1;
flex-direction: column;
align-items: flex-start;
gap: 2px;
min-width: 0;
}
.field-label {
.partner-name {
font-size: var(--text-sm);
font-weight: var(--font-semibold);
color: var(--text-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.partner-meta {
font-size: var(--text-xs);
color: var(--text-color-secondary);
}
.field-value {
font-size: var(--text-sm);
color: var(--text-color);
font-weight: var(--font-medium);
.partner-total {
display: flex;
align-items: center;
gap: var(--space-xs);
flex-shrink: 0;
}
.sold-value {
.partner-sold {
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: var(--font-bold);
color: var(--text-color);
}
.card-status {
display: inline-flex;
align-self: flex-start;
font-size: var(--text-xs);
padding: 2px var(--space-sm);
border-radius: var(--radius-full);
font-weight: var(--font-medium);
}
.status-ok {
background: var(--green-100);
color: var(--green-600);
}
.status-restant {
background: var(--red-100);
.partner-sold.overdue {
color: var(--red-600);
}
/* Sub items for expanded groups */
.card-sub-items {
.partner-chevron {
font-size: 12px;
color: var(--text-color-secondary);
}
.partner-invoices {
border-top: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md) var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.sub-item {
padding: var(--space-sm);
background: var(--surface-hover);
border-radius: var(--radius-sm);
}
.sub-item-header {
.invoice-line {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
font-weight: var(--font-medium);
margin-bottom: 4px;
padding: var(--space-xs) var(--space-md);
border-bottom: 1px solid var(--surface-border);
gap: var(--space-sm);
}
.sub-item-sold {
font-family: var(--font-mono);
font-weight: var(--font-bold);
.invoice-line:last-child {
border-bottom: none;
}
.sub-item-dates {
display: flex;
justify-content: space-between;
.invoice-ref {
font-size: var(--text-xs);
color: var(--text-color-secondary);
flex: 1;
min-width: 0;
}
.invoice-sold {
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-color);
flex-shrink: 0;
}
.invoice-sold.overdue {
color: var(--red-600);
}
/* Empty data state */
@@ -1710,39 +1585,34 @@ onUnmounted(() => {
justify-content: center;
}
/* Totals Summary */
.totals-summary {
display: flex;
gap: var(--space-lg);
padding: var(--space-md);
/* Desktop Totals - uses global stats.css (src/assets/css/components/stats.css) */
/* Mobile Totals Bar */
.mobile-totals-bar {
background: var(--surface-card);
border: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
}
@media (max-width: 768px) {
.totals-summary {
flex-direction: column;
gap: var(--space-sm);
}
}
.total-item {
.mobile-totals-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-sm);
}
.total-label {
.mobile-totals-bar .total-label {
font-size: var(--text-sm);
color: var(--text-color-secondary);
font-weight: var(--font-medium);
}
.total-value {
.mobile-totals-bar .total-value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
font-family: var(--font-mono);
color: var(--color-primary);
color: var(--text-color);
}
/* Filter Sheet Content */
@@ -1787,18 +1657,9 @@ onUnmounted(() => {
background: var(--primary-800);
}
[data-theme="dark"] .card-badge {
background: var(--primary-800);
}
[data-theme="dark"] .status-ok {
background: var(--green-900);
color: var(--green-300);
}
[data-theme="dark"] .status-restant {
background: var(--red-900);
color: var(--red-300);
[data-theme="dark"] .partner-sold.overdue,
[data-theme="dark"] .invoice-sold.overdue {
color: var(--red-400);
}
@media (prefers-color-scheme: dark) {
@@ -1810,18 +1671,9 @@ onUnmounted(() => {
background: var(--primary-800);
}
:root:not([data-theme]) .card-badge {
background: var(--primary-800);
}
:root:not([data-theme]) .status-ok {
background: var(--green-900);
color: var(--green-300);
}
:root:not([data-theme]) .status-restant {
background: var(--red-900);
color: var(--red-300);
:root:not([data-theme]) .partner-sold.overdue,
:root:not([data-theme]) .invoice-sold.overdue {
color: var(--red-400);
}
}
</style>

View File

@@ -104,7 +104,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="clearFilters(); showFilters = false"
/>
<Button
@@ -149,7 +150,7 @@
<div v-if="isMobile && companyStore.selectedCompany && invoicesStore.hasInvoices" class="mobile-totals-bar">
<div class="mobile-totals-content">
<span class="total-label">Sold Total:</span>
<span class="total-value" :class="invoicesStore.totalSoldAll > 0 ? 'positive' : 'negative'">
<span class="total-value">
{{ formatCompact(invoicesStore.totalSoldAll) }}
</span>
</div>
@@ -224,21 +225,16 @@
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="clearFilters"
/>
<Button
icon="pi pi-file-excel"
label="Export Excel"
class="p-button-outlined p-button-success"
<SplitButton
label="Export"
icon="pi pi-download"
:model="desktopExportItems"
@click="exportExcel"
:disabled="!invoicesStore.hasInvoices"
/>
<Button
icon="pi pi-file-pdf"
label="Export PDF"
class="p-button-outlined p-button-danger"
@click="exportPDF"
outlined
:disabled="!invoicesStore.hasInvoices"
/>
<Button
@@ -257,7 +253,7 @@
<div v-if="!isMobile && companyStore.selectedCompany && invoicesStore.hasInvoices" class="summary-stats-inline">
<div class="stat-item">
<span class="stat-label">Total Sold:</span>
<span class="stat-value" :class="invoicesStore.totalSoldAll > 0 ? 'plati' : 'incasari'">
<span class="stat-value">
{{ formatCurrency(invoicesStore.totalSoldAll) }}
</span>
</div>
@@ -266,23 +262,19 @@
<!-- Invoices Table -->
<Card v-if="companyStore.selectedCompany" class="table-card">
<template #content>
<!-- Mobile: Card Layout -->
<div v-if="isMobile" class="mobile-card-list">
<!-- Mobile: Flat List (compact, table-like) -->
<div v-if="isMobile" class="mobile-flat-list">
<div
v-for="invoice in invoicesStore.invoiceList"
:key="invoice.nract"
class="mobile-data-card"
class="flat-row"
>
<div class="card-header">{{ invoice.nume }}</div>
<div class="card-row">
<span>{{ formatDate(invoice.dataact) }} · {{ invoice.nract }}</span>
<span
class="card-amount"
:class="{ positive: invoice.soldfinal > 0 }"
>
{{ formatNumber(invoice.soldfinal) }}
</span>
<div class="flat-row-left">
<span class="flat-cont">{{ invoice.nract }}</span>
<span class="flat-date">{{ formatDateShort(invoice.dataact) }}</span>
<span class="flat-denumire">{{ invoice.nume || '' }}</span>
</div>
<span class="flat-sold">{{ formatNumber(invoice.soldfinal) }}</span>
</div>
<div v-if="invoicesStore.invoiceList.length === 0" class="mobile-empty">
<i class="pi pi-info-circle"></i>
@@ -412,6 +404,7 @@ 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 MobileDrawerMenu from "@shared/components/mobile/MobileDrawerMenu.vue";
import SplitButton from "primevue/splitbutton";
import { useRouter, useRoute } from "vue-router";
const toast = useToast();
@@ -591,12 +584,26 @@ const paymentStatusOptions = [
{ label: "Toate", value: "toate" },
];
// Desktop export dropdown items (SplitButton)
const desktopExportItems = [
{
label: "Export Excel",
icon: "pi pi-file-excel",
command: () => exportExcel(),
},
{
label: "Export PDF",
icon: "pi pi-file-pdf",
command: () => exportPDF(),
},
];
// Methods
const formatCurrency = (amount) => {
if (!amount) return "0,00 RON";
if (!amount || amount === 0) return "0,00";
return new Intl.NumberFormat("ro-RO", {
style: "currency",
currency: "RON",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
};
@@ -631,6 +638,16 @@ const formatDate = (dateString) => {
}
};
// Short date format for mobile (DD/MM only)
const formatDateShort = (dateString) => {
if (!dateString) return "";
try {
return format(new Date(dateString), "dd/MM", { locale: ro });
} catch (error) {
return "";
}
};
const handleCompanyChange = async () => {
if (!selectedCompanyId.value) return;
@@ -1157,55 +1174,66 @@ watch(
.mobile-totals-bar .total-value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
color: var(--text-color);
}
.mobile-totals-bar .total-value.positive {
color: var(--green-600);
}
.mobile-totals-bar .total-value.negative {
color: var(--red-600);
}
/* Colors removed - totals use neutral --text-color like TrialBalance */
/* ================================================
US-107: Mobile Card List (Invoice Cards)
Mobile Flat List (Compact Invoice Rows - like TrialBalance)
================================================ */
.mobile-card-list {
.mobile-flat-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-data-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.mobile-data-card .card-header {
font-weight: var(--font-semibold);
color: var(--text-color);
margin-bottom: var(--space-xs);
font-size: var(--text-base);
}
.mobile-data-card .card-row {
.flat-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
color: var(--text-color-secondary);
border-bottom: 1px solid var(--surface-border);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
}
.mobile-data-card .card-amount {
font-weight: var(--font-semibold);
.flat-row-left {
display: flex;
align-items: center;
gap: var(--space-sm);
flex: 1;
min-width: 0;
}
.flat-cont {
font-weight: var(--font-bold);
color: var(--text-color);
font-size: var(--text-sm);
min-width: 48px;
flex-shrink: 0;
}
.mobile-data-card .card-amount.positive {
color: var(--green-600);
.flat-date {
font-size: var(--text-xs);
color: var(--text-color-secondary);
flex-shrink: 0;
}
.flat-denumire {
color: var(--text-color-secondary);
font-size: var(--text-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flat-sold {
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
color: var(--text-color);
text-align: right;
flex-shrink: 0;
}
.mobile-empty {
@@ -1293,83 +1321,19 @@ watch(
text-align: center;
}
/* ================================================
Summary Stats
================================================ */
.summary-stats-inline {
display: flex;
justify-content: flex-end;
margin-bottom: var(--space-md);
}
.stat-item {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.stat-label {
font-size: var(--text-sm);
color: var(--text-color-secondary);
}
.stat-value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
}
.stat-value.plati {
color: var(--red-600);
}
.stat-value.incasari {
color: var(--green-600);
}
/* Summary Stats - uses global stats.css (src/assets/css/components/stats.css) */
/* ================================================
Dark Mode Support
================================================ */
[data-theme="dark"] .mobile-totals-bar .total-value.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-totals-bar .total-value.negative {
color: var(--red-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);
}
[data-theme="dark"] .stat-value.plati {
color: var(--red-400);
}
[data-theme="dark"] .stat-value.incasari {
color: var(--green-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-totals-bar .total-value.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-totals-bar .total-value.negative {
color: var(--red-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);

View File

@@ -57,7 +57,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="clearFilters(); showFilters = false"
/>
<Button
@@ -151,7 +152,8 @@
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
class="p-button-outlined p-button-secondary"
severity="secondary"
outlined
@click="clearFilters"
/>
<SplitButton
@@ -159,7 +161,7 @@
icon="pi pi-download"
:model="desktopExportItems"
@click="exportPDF"
class="p-button-outlined"
outlined
:disabled="!trialBalanceStore.hasData"
/>
<Button
@@ -205,26 +207,24 @@
<!-- Trial Balance Table -->
<Card v-if="companyStore.selectedCompany" class="table-card">
<template #content>
<!-- Mobile: Card Layout -->
<div v-if="isMobile" class="mobile-card-list">
<!-- Mobile: Flat Single-Row List -->
<div v-if="isMobile" class="mobile-flat-list">
<div
v-for="account in trialBalanceStore.trialBalanceData.filter(a => a.sold_final_debit > 0 || a.sold_final_credit > 0)"
v-for="account in filteredMobileAccounts"
:key="account.cont"
class="mobile-data-card"
class="flat-row"
>
<div class="card-header">
<strong>{{ account.cont }}</strong>&nbsp;&nbsp;{{ truncate(account.denumire, 30) }}
<div class="flat-row-left">
<span class="flat-cont">{{ account.cont }}</span>
<span class="flat-denumire">{{ account.denumire }}</span>
</div>
<div class="card-row">
<span></span>
<span class="card-amount">
<span class="flat-sold">
{{ account.sold_final_debit > 0
? formatCurrency(account.sold_final_debit) + ' D'
: formatCurrency(account.sold_final_credit) + ' C' }}
? formatCompact(account.sold_final_debit) + ' D'
: formatCompact(account.sold_final_credit) + ' C' }}
</span>
</div>
</div>
<div v-if="trialBalanceStore.trialBalanceData.filter(a => a.sold_final_debit > 0 || a.sold_final_credit > 0).length === 0" class="mobile-empty">
<div v-if="filteredMobileAccounts.length === 0" class="mobile-empty">
<i class="pi pi-info-circle"></i>
<p>Nu au fost găsite date</p>
</div>
@@ -506,6 +506,11 @@ const hasActiveFilters = computed(() => {
return localFilters.value.cont !== "" || localFilters.value.denumire !== "";
});
// Mobile: Accounts with non-zero final balance (avoids inline .filter() in template)
const filteredMobileAccounts = computed(() =>
trialBalanceStore.trialBalanceData.filter(a => a.sold_final_debit > 0 || a.sold_final_credit > 0)
);
// Mobile: Actions menu items
const actionMenuItems = computed(() => [
{
@@ -1123,40 +1128,55 @@ watch(
}
/* ================================================
US-108: Mobile Card List (Account Cards)
Mobile Flat List (Account Rows)
================================================ */
.mobile-card-list {
.mobile-flat-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-data-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.mobile-data-card .card-header {
font-weight: var(--font-semibold);
color: var(--text-color);
margin-bottom: var(--space-xs);
font-size: var(--text-base);
}
.mobile-data-card .card-row {
.flat-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
color: var(--text-color-secondary);
border-bottom: 1px solid var(--surface-border);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
gap: var(--space-sm);
}
.mobile-data-card .card-amount {
font-weight: var(--font-semibold);
.flat-row-left {
display: flex;
align-items: center;
gap: var(--space-sm);
flex: 1;
min-width: 0;
}
.flat-cont {
font-weight: var(--font-bold);
color: var(--text-color);
font-size: var(--text-sm);
min-width: 48px;
flex-shrink: 0;
}
.flat-denumire {
color: var(--text-color-secondary);
font-size: var(--text-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flat-sold {
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
color: var(--text-color);
text-align: right;
flex-shrink: 0;
}
.mobile-empty {

View File

@@ -44,7 +44,7 @@
* Default items (4 links):
* - Dashboard (/dashboard)
* - Bonuri (/data-entry)
* - Facturi (/reports/invoices)
* - Detalii (/reports/detailed-invoices)
* - Setări (/settings)
*/
@@ -58,7 +58,7 @@ defineProps({
default: () => [
{ to: '/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/data-entry', icon: 'pi pi-shopping-bag', label: 'Bonuri' },
{ to: '/reports/invoices', icon: 'pi pi-file-edit', label: 'Facturi' },
{ to: '/reports/detailed-invoices', icon: 'pi pi-file-edit', label: 'Detalii' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
],
validator: (items) => {

View File

@@ -715,18 +715,18 @@ const principaleItems = [
{ to: '/data-entry', icon: 'pi pi-shopping-bag', label: 'Bonuri', exactMatch: false }
]
// RAPOARTE: Facturi, Balanță, Casă, Bancă (US-519: separate pages)
// RAPOARTE: Facturi, Facturi Detaliate, Balanță, Casă, Bancă (US-519: separate pages)
const rapoarteItems = [
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi', exactMatch: true },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță', exactMatch: true },
{ to: '/reports/cash', icon: 'pi pi-wallet', label: 'Casă', exactMatch: true },
{ to: '/reports/bank', icon: 'pi pi-building', label: 'Bancă', exactMatch: true }
]
// ANALIZE: Scadențe, Facturi Detaliate
// ANALIZE: Scadențe
const analizeItems = [
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true }
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true }
]
// ADMINISTRARE: Setări