feat: Add A-Z filter for clients/suppliers in Telegram bot
- Add A-Z alphabetical filter keyboard for clients and suppliers lists (same pattern as company selection, without emoji) - Increase clients/suppliers list pagination from 10 to 20 items per page - Remove emoji from company A-Z filter button for consistency - Add 6 new callback handlers: clients_alpha_menu, clients_alpha:LETTER, clients_alpha_page:PAGE:LETTER, and supplier equivalents - Dashboard service and models updates - Telegram bot: email handlers, auth, DB operations, internal API improvements - Frontend: dashboard cards updates (CashFlow, Clienti, Furnizori, Treasury) - Frontend: SolduriCompactCard and CollapsibleCard improvements - DashboardView enhancements - start.sh and run-with-restart.sh script updates - IIS web.config and service worker updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -139,12 +139,11 @@ const toggleChartsExpanded = () => {
|
||||
chartsExpanded.value = !chartsExpanded.value;
|
||||
};
|
||||
|
||||
// Format currency
|
||||
// Format amount (without RON suffix)
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return "0 RON";
|
||||
if (!amount && amount !== 0) return "0";
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(Math.abs(amount));
|
||||
@@ -303,8 +302,7 @@ const initializeInflowsChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -513,8 +511,7 @@ const initializeOutflowsChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -618,6 +615,18 @@ onBeforeUnmount(() => {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
/* Metric label and value typography */
|
||||
.metric-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-bold);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
/* Split layout: Încasări | Divider | Plăți */
|
||||
.values-section {
|
||||
display: grid;
|
||||
|
||||
@@ -155,12 +155,11 @@ const toggleChartsExpanded = () => {
|
||||
chartsExpanded.value = !chartsExpanded.value;
|
||||
};
|
||||
|
||||
// Format currency
|
||||
// Format amount (without RON suffix)
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return "0 RON";
|
||||
if (!amount && amount !== 0) return "0";
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(Math.abs(amount));
|
||||
@@ -370,8 +369,7 @@ const initializeChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -496,7 +494,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.header-label {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-color);
|
||||
}
|
||||
@@ -508,11 +506,31 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.header-total {
|
||||
font-size: var(--text-xl);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-bold);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
/* Breakdown section typography */
|
||||
.breakdown-label {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.breakdown-value {
|
||||
font-size: var(--text-sm);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.breakdown-sublabel {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.breakdown-subvalue {
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.header-total.positive {
|
||||
color: var(--green-600);
|
||||
}
|
||||
|
||||
@@ -155,12 +155,11 @@ const toggleChartsExpanded = () => {
|
||||
chartsExpanded.value = !chartsExpanded.value;
|
||||
};
|
||||
|
||||
// Format currency
|
||||
// Format amount (without RON suffix)
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return "0 RON";
|
||||
if (!amount && amount !== 0) return "0";
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(Math.abs(amount));
|
||||
@@ -370,8 +369,7 @@ const initializeChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -496,7 +494,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.header-label {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-color);
|
||||
}
|
||||
@@ -508,11 +506,31 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.header-total {
|
||||
font-size: var(--text-xl);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-bold);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
/* Breakdown section typography */
|
||||
.breakdown-label {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.breakdown-value {
|
||||
font-size: var(--text-sm);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.breakdown-sublabel {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.breakdown-subvalue {
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.header-total.positive {
|
||||
color: var(--green-600);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Treasury items - Casa și Bancă stacked vertical cu expand individual -->
|
||||
<div class="treasury-items">
|
||||
<!-- Casa -->
|
||||
<div class="treasury-group" v-if="casaItems.length > 0 || casaTotal > 0">
|
||||
<div class="treasury-group" v-if="casaItems.length > 0 || casaTotal !== 0">
|
||||
<div class="treasury-header" @click="toggleCasaExpanded">
|
||||
<div class="treasury-header-left">
|
||||
<i
|
||||
@@ -12,7 +12,7 @@
|
||||
></i>
|
||||
<span class="treasury-label">Casa</span>
|
||||
</div>
|
||||
<span class="treasury-value text-success">{{ formatCurrency(casaTotal) }}</span>
|
||||
<span class="treasury-value" :class="casaTotal >= 0 ? 'text-success' : 'text-danger'">{{ formatCurrency(casaTotal) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Casa Sub-items -->
|
||||
@@ -26,13 +26,13 @@
|
||||
{{ item.nume || `Cont ${item.cont}` }}
|
||||
<span v-if="item.cont" class="treasury-cont">({{ item.cont }})</span>
|
||||
</span>
|
||||
<span class="treasury-subvalue">{{ formatCurrency(item.sold) }}</span>
|
||||
<span class="treasury-subvalue" :class="{ 'text-danger': item.sold < 0 }">{{ formatCurrency(item.sold) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bancă -->
|
||||
<div class="treasury-group" v-if="bancaItems.length > 0 || bancaTotal > 0">
|
||||
<div class="treasury-group" v-if="bancaItems.length > 0 || bancaTotal !== 0">
|
||||
<div class="treasury-header" @click="toggleBancaExpanded">
|
||||
<div class="treasury-header-left">
|
||||
<i
|
||||
@@ -41,7 +41,7 @@
|
||||
></i>
|
||||
<span class="treasury-label">Bancă</span>
|
||||
</div>
|
||||
<span class="treasury-value text-primary">{{ formatCurrency(bancaTotal) }}</span>
|
||||
<span class="treasury-value" :class="bancaTotal >= 0 ? 'text-primary' : 'text-danger'">{{ formatCurrency(bancaTotal) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Bancă Sub-items -->
|
||||
@@ -55,7 +55,7 @@
|
||||
{{ item.nume || `Cont ${item.cont}` }}
|
||||
<span v-if="item.cont" class="treasury-cont">({{ item.cont }})</span>
|
||||
</span>
|
||||
<span class="treasury-subvalue">{{ formatCurrency(item.sold) }}</span>
|
||||
<span class="treasury-subvalue" :class="{ 'text-danger': item.sold < 0 }">{{ formatCurrency(item.sold) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,15 +189,14 @@ const toggleChartsExpanded = () => {
|
||||
chartsExpanded.value = !chartsExpanded.value;
|
||||
};
|
||||
|
||||
// Format currency
|
||||
// Format amount (without RON suffix)
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return "0 RON";
|
||||
if (!amount && amount !== 0) return "0";
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(Math.abs(amount));
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
// Check if sparkline data exists
|
||||
@@ -327,8 +326,7 @@ const initializeCasaChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -511,8 +509,7 @@ const initializeBancaChart = async () => {
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
@@ -663,13 +660,13 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.treasury-label {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.treasury-value {
|
||||
font-size: var(--text-xl);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-bold);
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
@@ -775,10 +772,6 @@ onBeforeUnmount(() => {
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.treasury-value {
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.sparkline-chart {
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="solduri-compact-card__content">
|
||||
<span class="solduri-compact-card__label">{{ label }}</span>
|
||||
<span class="solduri-compact-card__value" :class="valueColorClass">
|
||||
{{ formatCurrency(total) }}
|
||||
{{ formatAmount(total) }}
|
||||
</span>
|
||||
</div>
|
||||
<i
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- Casa Total -->
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">Casa</span>
|
||||
<span class="solduri-compact-card__breakdown-value">{{ formatCurrency(casaTotal) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-value">{{ formatAmount(casaTotal) }}</span>
|
||||
</div>
|
||||
<!-- Sub-conturi Casa (imediat sub Casa) -->
|
||||
<template v-if="breakdown?.casa?.items?.length">
|
||||
@@ -37,13 +37,13 @@
|
||||
<span class="solduri-compact-card__breakdown-sublabel">
|
||||
{{ item.nume || `Cont ${item.cont}` }}
|
||||
</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatCurrency(item.sold) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatAmount(item.sold) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Bancă Total -->
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">Bancă</span>
|
||||
<span class="solduri-compact-card__breakdown-value">{{ formatCurrency(bancaTotal) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-value">{{ formatAmount(bancaTotal) }}</span>
|
||||
</div>
|
||||
<!-- Sub-conturi Bancă (imediat sub Bancă) -->
|
||||
<template v-if="breakdown?.banca?.items?.length">
|
||||
@@ -55,7 +55,7 @@
|
||||
<span class="solduri-compact-card__breakdown-sublabel">
|
||||
{{ item.nume || `Cont ${item.cont}` }}
|
||||
</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatCurrency(item.sold) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatAmount(item.sold) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -65,13 +65,13 @@
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">În termen</span>
|
||||
<span class="solduri-compact-card__breakdown-value">
|
||||
{{ formatCurrency(breakdown?.in_termen?.total || 0) }}
|
||||
{{ formatAmount(breakdown?.in_termen?.total || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">Restant</span>
|
||||
<span class="solduri-compact-card__breakdown-value solduri-compact-card__breakdown-value--warning">
|
||||
{{ formatCurrency(breakdown?.restant?.total || 0) }}
|
||||
{{ formatAmount(breakdown?.restant?.total || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Perioade restante -->
|
||||
@@ -82,24 +82,50 @@
|
||||
class="solduri-compact-card__breakdown-subitem"
|
||||
>
|
||||
<span class="solduri-compact-card__breakdown-sublabel">{{ formatPeriodLabel(key) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatCurrency(value) }}</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">{{ formatAmount(value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- TVA: Simple display (no breakdown needed) -->
|
||||
<!-- TVA / Datorii Buget: Breakdown pe grupe (TVA/BASS/CAM) cu sub-conturi -->
|
||||
<template v-else-if="type === 'tva'">
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">
|
||||
{{ total >= 0 ? 'TVA de recuperat' : 'TVA de plată' }}
|
||||
</span>
|
||||
<span
|
||||
class="solduri-compact-card__breakdown-value"
|
||||
:class="total >= 0 ? 'solduri-compact-card__breakdown-value--success' : 'solduri-compact-card__breakdown-value--danger'"
|
||||
>
|
||||
{{ formatCurrency(Math.abs(total)) }}
|
||||
</span>
|
||||
</div>
|
||||
<template v-if="Array.isArray(breakdown) && (breakdown as any[]).length">
|
||||
<div v-for="group in (breakdown as any[])" :key="group.key">
|
||||
<!-- Rând grup -->
|
||||
<div
|
||||
class="solduri-compact-card__breakdown-item solduri-compact-card__breakdown-group"
|
||||
@click.stop="toggleGroup(group.key)"
|
||||
>
|
||||
<span class="solduri-compact-card__breakdown-label solduri-compact-card__group-label">
|
||||
<i class="pi pi-chevron-right solduri-compact-card__group-toggle"
|
||||
:class="{ 'solduri-compact-card__group-toggle--expanded': expandedGroups.has(group.key) }"></i>
|
||||
{{ group.label }}
|
||||
</span>
|
||||
<span class="solduri-compact-card__breakdown-value">
|
||||
{{ group.precedent !== 0 ? formatAmount(group.precedent) : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Sub-conturi -->
|
||||
<div v-show="expandedGroups.has(group.key)">
|
||||
<div
|
||||
v-for="acc in group.sub_accounts"
|
||||
:key="acc.cont"
|
||||
class="solduri-compact-card__breakdown-subitem"
|
||||
>
|
||||
<span class="solduri-compact-card__breakdown-sublabel">{{ acc.label }}</span>
|
||||
<span class="solduri-compact-card__breakdown-subvalue">
|
||||
{{ acc.precedent !== 0 ? formatAmount(acc.precedent) : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="solduri-compact-card__breakdown-item">
|
||||
<span class="solduri-compact-card__breakdown-label">Fără date</span>
|
||||
<span class="solduri-compact-card__breakdown-value">-</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,6 +170,15 @@ const props = defineProps<{
|
||||
|
||||
// State
|
||||
const isExpanded = ref(false)
|
||||
const expandedGroups = ref(new Set<string>())
|
||||
const toggleGroup = (key: string) => {
|
||||
if (expandedGroups.value.has(key)) {
|
||||
expandedGroups.value.delete(key)
|
||||
} else {
|
||||
expandedGroups.value.add(key)
|
||||
}
|
||||
expandedGroups.value = new Set(expandedGroups.value)
|
||||
}
|
||||
|
||||
// Computed: Label based on type
|
||||
const label = computed(() => {
|
||||
@@ -151,7 +186,7 @@ const label = computed(() => {
|
||||
trezorerie: 'TREZORERIE',
|
||||
clienti: 'CLIENȚI',
|
||||
furnizori: 'FURNIZORI',
|
||||
tva: 'TVA'
|
||||
tva: 'DATORII BUGET'
|
||||
}
|
||||
return labels[props.type] || props.type.toUpperCase()
|
||||
})
|
||||
@@ -159,9 +194,11 @@ const label = computed(() => {
|
||||
// Computed: Value color class based on type and value
|
||||
const valueColorClass = computed(() => {
|
||||
if (props.type === 'tva') {
|
||||
return props.total >= 0
|
||||
? 'solduri-compact-card__value--success'
|
||||
: 'solduri-compact-card__value--danger'
|
||||
// total = tvaPreviousMonthNet = plata - recuperat
|
||||
// pozitiv = datorie la buget (roșu), zero/negativ = fără datorie (verde)
|
||||
return props.total > 0
|
||||
? 'solduri-compact-card__value--danger'
|
||||
: 'solduri-compact-card__value--success'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
@@ -175,7 +212,7 @@ const hasBreakdown = computed(() => {
|
||||
return props.breakdown !== null && props.breakdown !== undefined
|
||||
}
|
||||
if (props.type === 'tva') {
|
||||
return true // TVA always shows status
|
||||
return props.breakdown !== null && props.breakdown !== undefined
|
||||
}
|
||||
return false
|
||||
})
|
||||
@@ -187,11 +224,10 @@ const toggleExpanded = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const formatCurrency = (amount: number | undefined | null): string => {
|
||||
if (amount === undefined || amount === null) return '0 RON'
|
||||
const formatAmount = (amount: number | undefined | null): string => {
|
||||
if (amount === undefined || amount === null) return '0'
|
||||
return new Intl.NumberFormat('ro-RO', {
|
||||
style: 'currency',
|
||||
currency: 'RON',
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(amount)
|
||||
@@ -304,13 +340,13 @@ const formatPeriodLabel = (key: string): string => {
|
||||
}
|
||||
|
||||
.solduri-compact-card__breakdown-label {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.solduri-compact-card__breakdown-value {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-mono, monospace);
|
||||
@@ -349,6 +385,26 @@ const formatPeriodLabel = (key: string): string => {
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
/* TVA grup toggle */
|
||||
.solduri-compact-card__breakdown-group {
|
||||
cursor: pointer;
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
.solduri-compact-card__breakdown-group:hover { background: var(--surface-hover); }
|
||||
|
||||
.solduri-compact-card__group-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.solduri-compact-card__group-toggle {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-secondary);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
.solduri-compact-card__group-toggle--expanded { transform: rotate(90deg); }
|
||||
|
||||
/* Responsive - Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.solduri-compact-card {
|
||||
|
||||
@@ -41,8 +41,13 @@
|
||||
|
||||
<!-- Company selection removed - now handled in header only -->
|
||||
|
||||
<!-- Loading Bar - non-blocking, subtle indicator while data loads -->
|
||||
<div v-if="isLoading" class="loading-bar-container">
|
||||
<div class="loading-bar"></div>
|
||||
</div>
|
||||
|
||||
<!-- Secțiune Carduri Noi - Adăugare -->
|
||||
<div class="metrics-cards-section" v-if="!isLoading">
|
||||
<div class="metrics-cards-section">
|
||||
<!-- Mobile: Swipeable KPI Cards Carousel -->
|
||||
<!-- US-2002: 6 pages - first page is 2x2 grid with solduri, pages 2-5 are original graph cards, page 6 is financial indicators -->
|
||||
<SwipeableCards v-if="isMobile" :totalCards="6" class="mobile-kpi-carousel">
|
||||
@@ -68,7 +73,8 @@
|
||||
/>
|
||||
<SolduriCompactCard
|
||||
type="tva"
|
||||
:total="tvaTotal"
|
||||
:total="tvaPreviousMonthNet"
|
||||
:breakdown="tvaBreakdown"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -220,12 +226,66 @@
|
||||
:cacheInfo="netBalanceCacheInfo"
|
||||
/>
|
||||
</CollapsibleCard>
|
||||
<CollapsibleCard
|
||||
label="Datorii la buget"
|
||||
:value="budgetDebtTotalPrecedent"
|
||||
:value-class="budgetDebtTotalPrecedent > 0 ? 'negative' : 'positive'"
|
||||
>
|
||||
<div class="budget-debt-breakdown-desktop">
|
||||
<div class="budget-debt-breakdown-header">
|
||||
<span class="budget-debt-col-label">Categorie</span>
|
||||
<span class="budget-debt-col-header-value">Luna prec.</span>
|
||||
<span class="budget-debt-col-header-value">Luna curentă</span>
|
||||
</div>
|
||||
|
||||
<template v-for="group in budgetDebtBreakdown" :key="group.key">
|
||||
<!-- Rând grup (clickabil pentru expand sub-conturi) -->
|
||||
<div
|
||||
class="budget-debt-breakdown-row budget-debt-group-row"
|
||||
@click="toggleBudgetGroup(group.key)"
|
||||
>
|
||||
<span class="budget-debt-col-label budget-debt-group-label">
|
||||
<i class="pi pi-chevron-right budget-debt-toggle"
|
||||
:class="{ expanded: expandedBudgetGroups.has(group.key) }"></i>
|
||||
{{ group.label }}
|
||||
</span>
|
||||
<span class="budget-debt-col-value">
|
||||
{{ group.precedent !== 0 ? formatAmount(group.precedent) : '-' }}
|
||||
</span>
|
||||
<span class="budget-debt-col-value">
|
||||
{{ group.curent !== 0 ? formatAmount(group.curent) : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Sub-conturi (expandabile) -->
|
||||
<div v-show="expandedBudgetGroups.has(group.key)" class="budget-debt-sub-accounts">
|
||||
<div
|
||||
v-for="acc in group.sub_accounts"
|
||||
:key="acc.cont"
|
||||
class="budget-debt-breakdown-row budget-debt-subrow"
|
||||
>
|
||||
<span class="budget-debt-col-label budget-debt-sub-label">{{ acc.label }}</span>
|
||||
<span class="budget-debt-col-value budget-debt-sub-value">
|
||||
{{ acc.precedent !== 0 ? formatAmount(acc.precedent) : '-' }}
|
||||
</span>
|
||||
<span class="budget-debt-col-value budget-debt-sub-value">
|
||||
{{ acc.curent !== 0 ? formatAmount(acc.curent) : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="budgetDebtBreakdown.length === 0" class="budget-debt-breakdown-empty">
|
||||
Nu există datorii înregistrate
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleCard>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Financial Indicators Section - Desktop Only (US-014) -->
|
||||
<div v-if="!isMobile && !isLoading" class="financial-indicators-section">
|
||||
<div v-if="!isMobile" class="financial-indicators-section">
|
||||
<FinancialIndicatorsCard
|
||||
:loading="dashboardStore.financialIndicators.loading"
|
||||
:error="dashboardStore.financialIndicators.error"
|
||||
@@ -236,11 +296,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Se încarcă datele dashboard-ului...</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -507,10 +562,63 @@ const totalTrezorerie = computed(() => {
|
||||
});
|
||||
|
||||
const tvaTotal = computed(() => {
|
||||
// TVA from dashboard summary if available, otherwise default to 0
|
||||
return dashboardStore.summary?.tva_sold || 0;
|
||||
const s = dashboardStore.summary;
|
||||
if (!s) return 0;
|
||||
// Pozitiv = de recuperat, Negativ = de plată
|
||||
return Number(s.tva_recuperat_curent || 0) - Number(s.tva_plata_curent || 0);
|
||||
});
|
||||
|
||||
const tvaPreviousMonth = computed(() => {
|
||||
const s = dashboardStore.summary;
|
||||
if (!s) return { plata: 0, recuperat: 0 };
|
||||
return {
|
||||
plata: Number(s.tva_plata_precedent || 0),
|
||||
recuperat: Number(s.tva_recuperat_precedent || 0),
|
||||
};
|
||||
});
|
||||
|
||||
const tvaCurrentMonth = computed(() => {
|
||||
const s = dashboardStore.summary;
|
||||
if (!s) return { plata: 0, recuperat: 0 };
|
||||
return {
|
||||
plata: Number(s.tva_plata_curent || 0),
|
||||
recuperat: Number(s.tva_recuperat_curent || 0),
|
||||
};
|
||||
});
|
||||
|
||||
// TVA net luna precedentă (valoare pozitivă = datorie la buget)
|
||||
const tvaPreviousMonthNet = computed(() => {
|
||||
const prev = tvaPreviousMonth.value;
|
||||
return (prev.plata || 0) - (prev.recuperat || 0);
|
||||
});
|
||||
|
||||
// Breakdown pe grupe datorii buget (TVA/BASS/CAM cu sub-conturi)
|
||||
const budgetDebtBreakdown = computed(() => {
|
||||
return dashboardStore.summary?.budget_debt_breakdown || [];
|
||||
});
|
||||
|
||||
// Total precedent din toate grupurile (pentru header CollapsibleCard)
|
||||
const budgetDebtTotalPrecedent = computed(() => {
|
||||
const groups = dashboardStore.summary?.budget_debt_breakdown || [];
|
||||
return groups.reduce((sum, g) => sum + Number(g.precedent || 0), 0);
|
||||
});
|
||||
|
||||
const tvaBreakdown = computed(() => {
|
||||
return dashboardStore.summary?.budget_debt_breakdown || [];
|
||||
});
|
||||
|
||||
// Reactive state pentru expand/collapse grupe buget
|
||||
const expandedBudgetGroups = ref(new Set());
|
||||
const toggleBudgetGroup = (key) => {
|
||||
if (expandedBudgetGroups.value.has(key)) {
|
||||
expandedBudgetGroups.value.delete(key);
|
||||
} else {
|
||||
expandedBudgetGroups.value.add(key);
|
||||
}
|
||||
// Force reactivity update on Set
|
||||
expandedBudgetGroups.value = new Set(expandedBudgetGroups.value);
|
||||
};
|
||||
|
||||
// Net Cash Flow for CollapsibleCard header
|
||||
const netCashFlow = computed(() => {
|
||||
return (monthlyInflows.value || 0) - (monthlyOutflows.value || 0);
|
||||
@@ -716,18 +824,18 @@ const handleCompanyChanged = async (company) => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return "0,00 RON";
|
||||
const formatAmount = (amount) => {
|
||||
if (!amount && amount !== 0) return "0";
|
||||
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
||||
if (isNaN(numAmount)) return "0,00 RON";
|
||||
|
||||
if (isNaN(numAmount)) return "0";
|
||||
try {
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
style: "decimal",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(numAmount);
|
||||
} catch (error) {
|
||||
return `${numAmount.toLocaleString("ro-RO", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} RON`;
|
||||
} catch {
|
||||
return numAmount.toLocaleString("ro-RO", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1100,6 +1208,14 @@ const loadDashboardData = async () => {
|
||||
const indicatorAn = prevPeriod?.an || null;
|
||||
|
||||
try {
|
||||
// Fire financial indicators independently - it has its own loading state
|
||||
// and should not block the main dashboard cards from appearing
|
||||
dashboardStore.loadFinancialIndicators(
|
||||
companyStore.selectedCompany.id_firma,
|
||||
indicatorLuna,
|
||||
indicatorAn,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
dashboardStore.loadDashboardSummary(
|
||||
companyStore.selectedCompany.id_firma,
|
||||
@@ -1111,12 +1227,6 @@ const loadDashboardData = async () => {
|
||||
loadMonthlyFlows(),
|
||||
loadTreasuryBreakdown(),
|
||||
loadNetBalanceBreakdown(),
|
||||
// US-014: Load financial indicators for desktop card (luna anterioară)
|
||||
dashboardStore.loadFinancialIndicators(
|
||||
companyStore.selectedCompany.id_firma,
|
||||
indicatorLuna,
|
||||
indicatorAn,
|
||||
),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Failed to load dashboard data:", error);
|
||||
@@ -1447,15 +1557,26 @@ onUnmounted(() => {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Loading State - Uses global .loading-spinner from interactive.css */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-xl);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
/* Loading Bar - non-blocking, subtle indicator at top of content */
|
||||
.loading-bar-container {
|
||||
height: 3px;
|
||||
background: var(--surface-border);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
height: 100%;
|
||||
background: var(--color-primary);
|
||||
animation: loading-progress 1.5s ease-in-out infinite;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
@keyframes loading-progress {
|
||||
0% { width: 0%; transform: translateX(0%); }
|
||||
50% { width: 60%; transform: translateX(60%); }
|
||||
100% { width: 30%; transform: translateX(300%); }
|
||||
}
|
||||
|
||||
/* Responsive Design - Consolidated (Component-specific only) */
|
||||
@@ -1537,6 +1658,104 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Budget Debt Breakdown in Desktop CollapsibleCard */
|
||||
.budget-debt-breakdown-desktop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.budget-debt-breakdown-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-xs) 0 var(--space-sm) 0;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.budget-debt-breakdown-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-xs) 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.budget-debt-col-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.budget-debt-breakdown-header .budget-debt-col-label {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.budget-debt-col-value {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
font-family: var(--font-mono, monospace);
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
min-width: 130px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.budget-debt-col-header-value {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-secondary);
|
||||
white-space: nowrap;
|
||||
min-width: 130px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.budget-debt-breakdown-empty {
|
||||
padding: var(--space-sm) 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* Grup row - clickabil, bold */
|
||||
.budget-debt-group-row {
|
||||
cursor: pointer;
|
||||
font-weight: var(--font-semibold);
|
||||
background: var(--surface-ground);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.budget-debt-group-row:hover { background: var(--surface-hover); }
|
||||
|
||||
.budget-debt-group-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.budget-debt-toggle {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
.budget-debt-toggle.expanded { transform: rotate(90deg); }
|
||||
|
||||
/* Sub-conturi indentate */
|
||||
.budget-debt-sub-accounts {
|
||||
background: var(--surface-card);
|
||||
border-left: 2px solid var(--surface-border);
|
||||
margin-left: var(--space-sm);
|
||||
}
|
||||
|
||||
.budget-debt-subrow { opacity: 0.85; }
|
||||
.budget-debt-sub-label { padding-left: var(--space-md); font-size: var(--text-xs); }
|
||||
.budget-debt-sub-value { font-size: var(--text-xs); }
|
||||
|
||||
/* Responsive - All breakpoints consolidated */
|
||||
@media (max-width: 1200px) {
|
||||
.metrics-row {
|
||||
|
||||
@@ -76,8 +76,7 @@ const formattedValue = computed(() => {
|
||||
|
||||
if (props.formatCurrency && typeof props.value === 'number') {
|
||||
return new Intl.NumberFormat('ro-RO', {
|
||||
style: 'currency',
|
||||
currency: 'RON',
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(props.value)
|
||||
@@ -138,12 +137,14 @@ const toggleExpanded = () => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
/* Label - matches SolduriCompactCard style */
|
||||
.collapsible-card__label {
|
||||
font-size: var(--text-sm);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: var(--font-medium);
|
||||
font-weight: var(--font-semibold);
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Value */
|
||||
@@ -223,10 +224,6 @@ const toggleExpanded = () => {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
}
|
||||
|
||||
.collapsible-card__label {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.collapsible-card__value {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user