Files
roa2web-service-auto/src/modules/reports/views/FinancialIndicatorsView.vue
Claude Agent c5cfe3467c fix(design): standardize mobile breakpoint to <= 768 across all views
Align remaining 12 files with the corrected breakpoint from FINDING-005.
Using < 768 caused a 1px inconsistency at exactly 768px where CSS media
queries (max-width: 768px) triggered mobile but JS did not.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:41:17 +00:00

1086 lines
50 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container" :class="{ 'mobile-layout': isMobile }">
<!-- Mobile Material Design Top Bar -->
<MobileTopBar
v-if="isMobile"
title="Indicatori Financiari"
:show-menu="true"
:actions="topBarActions"
@menu-click="showDrawer = true"
@action-click="handleTopBarAction"
/>
<!-- Mobile Drawer Menu -->
<MobileDrawerMenu
v-model="showDrawer"
:user="authStore.user"
:companies-store="companyStore"
:period-store="periodStore"
:available-servers="authStore.availableServers"
:current-server-id="authStore.selectedServerId"
:auth-store="authStore"
@logout="handleLogout"
@server-switched="handleServerSwitched"
/>
<div class="indicators-view">
<!-- Page Header - Desktop only -->
<div class="page-header" v-if="!isMobile">
<h1 class="page-title">
<i class="pi pi-chart-bar"></i>
Indicatori Financiari
</h1>
</div>
<!-- Period filter bar - compact inline row -->
<div class="period-filter-bar">
<Dropdown
v-model="desktopSelectedPeriodKey"
:options="periodDropdownOptions"
option-label="label"
option-value="key"
placeholder="Selectați perioada"
class="period-dropdown"
@change="onDesktopPeriodChange"
/>
<Button
icon="pi pi-refresh"
:loading="loading"
@click="loadData"
severity="secondary"
outlined
class="refresh-btn"
/>
</div>
<!-- Loading State -->
<Card v-if="loading" class="state-card">
<template #content>
<div class="loading-state">
<ProgressSpinner />
<p>Se încarcă indicatorii financiari...</p>
</div>
</template>
</Card>
<!-- Error State -->
<Card v-else-if="error" class="state-card">
<template #content>
<div class="error-state">
<i class="pi pi-exclamation-circle error-icon"></i>
<p class="error-message">{{ error }}</p>
<Button
icon="pi pi-refresh"
label="Reîncearcă"
severity="secondary"
outlined
@click="loadData"
/>
</div>
</template>
</Card>
<!-- No company selected -->
<Card v-else-if="!companyStore.selectedCompany" class="state-card">
<template #content>
<div class="empty-state">
<i class="pi pi-building empty-icon"></i>
<p>Selectați o companie pentru a vizualiza indicatorii financiari.</p>
</div>
</template>
</Card>
<!-- No data yet -->
<Card v-else-if="!data" class="state-card">
<template #content>
<div class="empty-state">
<i class="pi pi-chart-bar empty-icon"></i>
<p>Nu există date disponibile pentru perioada selectată.</p>
<Button
icon="pi pi-refresh"
label="Încarcă Date"
@click="loadData"
/>
</div>
</template>
</Card>
<!-- Main Content: Indicator Sections -->
<Card v-else class="indicators-card">
<template #content>
<div class="indicators-sections">
<!-- Section 1: Lichiditate -->
<h3 v-if="data.lichiditate" class="section-heading section-heading-toggle" @click="toggleSubSection('lichiditate')">
Lichiditate
<i class="pi" :class="subSectionsExpanded.lichiditate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.lichiditate">
<IndicatorItem
v-if="data.lichiditate.lichiditate_curenta"
label="Lichiditate Curentă"
:value="data.lichiditate.lichiditate_curenta.value"
:status="data.lichiditate.lichiditate_curenta.status"
:sparkline-data="data.lichiditate.lichiditate_curenta.sparkline_data || []"
:sparkline-labels="data.lichiditate.lichiditate_curenta.sparkline_labels || []"
:thresholds="data.lichiditate.lichiditate_curenta"
:decimals="0"
description="Capacitatea de acoperire a datoriilor curente. Active Curente / Datorii Curente • Ideal: >1.5"
/>
<IndicatorItem
v-if="data.lichiditate.lichiditate_imediata"
label="Lichiditate Imediată"
:value="data.lichiditate.lichiditate_imediata.value"
:status="data.lichiditate.lichiditate_imediata.status"
:sparkline-data="data.lichiditate.lichiditate_imediata.sparkline_data || []"
:sparkline-labels="data.lichiditate.lichiditate_imediata.sparkline_labels || []"
:thresholds="data.lichiditate.lichiditate_imediata"
:decimals="0"
description="Capacitatea de plată imediată (fără stocuri). (Trezorerie + Clienți) / Furnizori • Ideal: >1.0"
/>
<IndicatorItem
v-if="data.lichiditate.lichiditate_vedere"
label="Lichiditate la Vedere"
:value="data.lichiditate.lichiditate_vedere.value"
:status="data.lichiditate.lichiditate_vedere.status"
:sparkline-data="data.lichiditate.lichiditate_vedere.sparkline_data || []"
:sparkline-labels="data.lichiditate.lichiditate_vedere.sparkline_labels || []"
:thresholds="data.lichiditate.lichiditate_vedere"
:decimals="0"
description="Numerar disponibil pentru plăți imediate. Trezorerie / Furnizori • Ideal: >0.2"
/>
<template v-if="subSectionsExpanded.lichiditate">
<div v-if="data.lichiditate.active_curente" class="sub-indicator-row">
<span class="sub-indicator-name"> Active Curente</span>
<span class="sub-indicator-desc">Stocuri + Creanțe + Disponibilități</span>
<span class="sub-indicator-value">{{ formatCurrency(data.lichiditate.active_curente.value) }}</span>
</div>
<div v-if="data.lichiditate.disponibilitati" class="sub-indicator-row">
<span class="sub-indicator-name"> Disponibilități</span>
<span class="sub-indicator-desc">Bancă (512x) + Casă (531x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.lichiditate.disponibilitati.value) }}</span>
</div>
<div v-if="data.lichiditate.creante" class="sub-indicator-row">
<span class="sub-indicator-name"> Creanțe</span>
<span class="sub-indicator-desc">Clienți (411x) + Debitori (461x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.lichiditate.creante.value) }}</span>
</div>
<div v-if="data.lichiditate.datorii_curente" class="sub-indicator-row">
<span class="sub-indicator-name"> Datorii Curente</span>
<span class="sub-indicator-desc">Furnizori + TVA + Salarii + etc.</span>
<span class="sub-indicator-value">{{ formatCurrency(data.lichiditate.datorii_curente.value) }}</span>
</div>
</template>
</template>
<!-- Section 2: Eficiență -->
<h3 v-if="data.eficienta" class="section-heading section-heading-toggle" @click="toggleSubSection('eficienta')">
Eficiență
<i class="pi" :class="subSectionsExpanded.eficienta ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.eficienta">
<IndicatorItem
v-if="data.eficienta.dso"
label="DSO (Zile încasare)"
:value="data.eficienta.dso.value"
:status="data.eficienta.dso.status"
:sparkline-data="data.eficienta.dso.sparkline_data || []"
:sparkline-labels="data.eficienta.dso.sparkline_labels || []"
:thresholds="data.eficienta.dso"
:decimals="0"
description="Câte zile durează încasarea creanțelor. (Sold Clienți / Facturări) × 30 • Ideal: <45 zile"
/>
<IndicatorItem
v-if="data.eficienta.dpo"
label="DPO (Zile plată furnizori)"
:value="data.eficienta.dpo.value"
:status="data.eficienta.dpo.status"
:sparkline-data="data.eficienta.dpo.sparkline_data || []"
:sparkline-labels="data.eficienta.dpo.sparkline_labels || []"
:thresholds="data.eficienta.dpo"
:decimals="0"
description="Câte zile durează plata furnizorilor. (Sold Furnizori / Achiziții) × 30 • Ideal: 30-60 zile"
/>
<IndicatorItem
v-if="data.eficienta.cash_conversion_cycle"
label="Ciclu Conversie Cash"
:value="data.eficienta.cash_conversion_cycle.value"
:status="data.eficienta.cash_conversion_cycle.status"
:sparkline-data="data.eficienta.cash_conversion_cycle.sparkline_data || []"
:sparkline-labels="data.eficienta.cash_conversion_cycle.sparkline_labels || []"
:thresholds="data.eficienta.cash_conversion_cycle"
:decimals="0"
description="Timpul de conversie în numerar. DSO - DPO • Ideal: cât mai mic (sau negativ)"
/>
<IndicatorItem
v-if="data.eficienta.rata_incasare"
label="Rata Încasare"
:value="data.eficienta.rata_incasare.value"
:status="data.eficienta.rata_incasare.status"
:sparkline-data="data.eficienta.rata_incasare.sparkline_data || []"
:sparkline-labels="data.eficienta.rata_incasare.sparkline_labels || []"
:thresholds="data.eficienta.rata_incasare"
:decimals="0"
description="Procentul facturilor încasate. Încasări / Facturări × 100 • Ideal: >85%"
/>
<IndicatorItem
v-if="data.eficienta.rata_plata"
label="Rata Plată"
:value="data.eficienta.rata_plata.value"
:status="data.eficienta.rata_plata.status"
:sparkline-data="data.eficienta.rata_plata.sparkline_data || []"
:sparkline-labels="data.eficienta.rata_plata.sparkline_labels || []"
:thresholds="data.eficienta.rata_plata"
:decimals="0"
description="Disciplina de plată către furnizori. Plăți / Achiziții × 100 • Ideal: 70-90%"
/>
<template v-if="subSectionsExpanded.eficienta">
<div v-if="data.eficienta.sold_clienti" class="sub-indicator-row">
<span class="sub-indicator-name"> Sold Clienți</span>
<span class="sub-indicator-desc">Sold clienți la final de lună</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.sold_clienti.value) }}</span>
</div>
<div v-if="data.eficienta.facturari_lunare" class="sub-indicator-row">
<span class="sub-indicator-name"> Facturări Lunare</span>
<span class="sub-indicator-desc">Media facturărilor (3 luni)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.facturari_lunare.value) }}</span>
</div>
<div v-if="data.eficienta.sold_furnizori" class="sub-indicator-row">
<span class="sub-indicator-name"> Sold Furnizori</span>
<span class="sub-indicator-desc">Sold furnizori la final de lună</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.sold_furnizori.value) }}</span>
</div>
<div v-if="data.eficienta.achizitii_lunare" class="sub-indicator-row">
<span class="sub-indicator-name"> Achiziții Lunare</span>
<span class="sub-indicator-desc">Media achizițiilor (3 luni)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.achizitii_lunare.value) }}</span>
</div>
<div v-if="data.eficienta.incasari_luna" class="sub-indicator-row">
<span class="sub-indicator-name"> Încasări Lunare</span>
<span class="sub-indicator-desc">Media încasărilor (3 luni)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.incasari_luna.value) }}</span>
</div>
<div v-if="data.eficienta.plati_luna" class="sub-indicator-row">
<span class="sub-indicator-name"> Plăți Lunare</span>
<span class="sub-indicator-desc">Media plăților (3 luni)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.eficienta.plati_luna.value) }}</span>
</div>
</template>
</template>
<!-- Section 3: Risc -->
<h3 v-if="data.risc" class="section-heading section-heading-toggle" @click="toggleSubSection('risc')">
Risc
<i class="pi" :class="subSectionsExpanded.risc ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.risc">
<IndicatorItem
v-if="data.risc.creante_restante_pct"
label="Creanțe Restante"
:value="data.risc.creante_restante_pct.value"
:status="data.risc.creante_restante_pct.status"
:sparkline-data="data.risc.creante_restante_pct.sparkline_data || []"
:sparkline-labels="data.risc.creante_restante_pct.sparkline_labels || []"
:thresholds="data.risc.creante_restante_pct"
:decimals="0"
description="Procentul facturilor depășite la plată. Restante / Total × 100 • Ideal: <20%"
/>
<IndicatorItem
v-if="data.risc.creante_90plus_pct"
label="Creanțe >90 zile"
:value="data.risc.creante_90plus_pct.value"
:status="data.risc.creante_90plus_pct.status"
:sparkline-data="data.risc.creante_90plus_pct.sparkline_data || []"
:sparkline-labels="data.risc.creante_90plus_pct.sparkline_labels || []"
:thresholds="data.risc.creante_90plus_pct"
:decimals="0"
description="Creanțe vechi cu risc mare de neîncasare. Restant 90+ / Total × 100 • Ideal: <5%"
/>
<IndicatorItem
v-if="data.risc.datorii_restante_pct"
label="Datorii Restante"
:value="data.risc.datorii_restante_pct.value"
:status="data.risc.datorii_restante_pct.status"
:sparkline-data="data.risc.datorii_restante_pct.sparkline_data || []"
:sparkline-labels="data.risc.datorii_restante_pct.sparkline_labels || []"
:thresholds="data.risc.datorii_restante_pct"
:decimals="0"
description="Disciplina de plată către furnizori. Restante / Total × 100 • Ideal: <10%"
/>
<IndicatorItem
v-if="data.risc.raport_datorii_trezorerie"
label="Datorii / Trezorerie"
:value="data.risc.raport_datorii_trezorerie.value"
:status="data.risc.raport_datorii_trezorerie.status"
:sparkline-data="data.risc.raport_datorii_trezorerie.sparkline_data || []"
:sparkline-labels="data.risc.raport_datorii_trezorerie.sparkline_labels || []"
:thresholds="data.risc.raport_datorii_trezorerie"
:decimals="0"
description="Câte lei de datorii pe fiecare leu în casă. Furnizori / Trezorerie • Ideal: <3.0"
/>
<template v-if="subSectionsExpanded.risc">
<div v-if="data.risc.total_clienti" class="sub-indicator-row">
<span class="sub-indicator-name"> Total Clienți</span>
<span class="sub-indicator-desc">Sold total clienți (411x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.total_clienti.value) }}</span>
</div>
<div v-if="data.risc.clienti_restanti" class="sub-indicator-row">
<span class="sub-indicator-name"> Clienți Restanți</span>
<span class="sub-indicator-desc">Sold clienți cu facturi restante</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.clienti_restanti.value) }}</span>
</div>
<div v-if="data.risc.clienti_90plus" class="sub-indicator-row">
<span class="sub-indicator-name"> Clienți >90 zile</span>
<span class="sub-indicator-desc">Sold clienți restant >90 zile</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.clienti_90plus.value) }}</span>
</div>
<div v-if="data.risc.total_furnizori" class="sub-indicator-row">
<span class="sub-indicator-name"> Total Furnizori</span>
<span class="sub-indicator-desc">Sold total furnizori (401x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.total_furnizori.value) }}</span>
</div>
<div v-if="data.risc.furnizori_restanti" class="sub-indicator-row">
<span class="sub-indicator-name"> Furnizori Restanți</span>
<span class="sub-indicator-desc">Sold furnizori cu facturi restante</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.furnizori_restanti.value) }}</span>
</div>
<div v-if="data.risc.trezorerie" class="sub-indicator-row">
<span class="sub-indicator-name"> Trezorerie</span>
<span class="sub-indicator-desc">Disponibilități (512x + 531x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.risc.trezorerie.value) }}</span>
</div>
</template>
</template>
<!-- Section 4: Cash Flow -->
<h3 v-if="data.cash_flow" class="section-heading section-heading-toggle" @click="toggleSubSection('cash_flow')">
Cash Flow
<i class="pi" :class="subSectionsExpanded.cash_flow ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.cash_flow">
<IndicatorItem
v-if="data.cash_flow.flux_net_lunar"
label="Flux Net Lunar"
:value="data.cash_flow.flux_net_lunar.value"
:status="data.cash_flow.flux_net_lunar.status"
:sparkline-data="data.cash_flow.flux_net_lunar.sparkline_data || []"
:sparkline-labels="data.cash_flow.flux_net_lunar.sparkline_labels || []"
:thresholds="data.cash_flow.flux_net_lunar"
:decimals="0"
description="Cash generat sau consumat luna curentă. Încasări - Plăți • Ideal: >0 (pozitiv)"
/>
<IndicatorItem
v-if="data.cash_flow.cash_flow_ytd"
label="Cash Flow YTD"
:value="data.cash_flow.cash_flow_ytd.value"
:status="data.cash_flow.cash_flow_ytd.status"
:sparkline-data="data.cash_flow.cash_flow_ytd.sparkline_data || []"
:sparkline-labels="data.cash_flow.cash_flow_ytd.sparkline_labels || []"
:thresholds="data.cash_flow.cash_flow_ytd"
:decimals="0"
description="Flux cumulat de la începutul anului. Σ(Încasări - Plăți) • Ideal: pozitiv"
/>
<IndicatorItem
v-if="data.cash_flow.flux_net_yoy_pct"
label="Flux Net YoY"
:value="data.cash_flow.flux_net_yoy_pct.value"
:status="data.cash_flow.flux_net_yoy_pct.status"
:sparkline-data="data.cash_flow.flux_net_yoy_pct.sparkline_data || []"
:sparkline-labels="data.cash_flow.flux_net_yoy_pct.sparkline_labels || []"
:thresholds="data.cash_flow.flux_net_yoy_pct"
:decimals="0"
description="Evoluția față de anul trecut. (CF curent - CF anterior) / CF anterior × 100 • Ideal: >0%"
/>
<IndicatorItem
v-if="data.cash_flow.acoperire_cash_flow"
label="Acoperire CF"
:value="data.cash_flow.acoperire_cash_flow.value"
:status="data.cash_flow.acoperire_cash_flow.status"
:sparkline-data="data.cash_flow.acoperire_cash_flow.sparkline_data || []"
:sparkline-labels="data.cash_flow.acoperire_cash_flow.sparkline_labels || []"
:thresholds="data.cash_flow.acoperire_cash_flow"
:decimals="0"
description="Capacitatea de plată din flux. Cash Flow / Datorii Restante • Ideal: >0.5"
/>
<template v-if="subSectionsExpanded.cash_flow">
<div v-if="data.cash_flow.incasari_luna" class="sub-indicator-row">
<span class="sub-indicator-name"> Încasări Luna</span>
<span class="sub-indicator-desc">Încasări luna curentă</span>
<span class="sub-indicator-value">{{ formatCurrency(data.cash_flow.incasari_luna.value) }}</span>
</div>
<div v-if="data.cash_flow.plati_luna" class="sub-indicator-row">
<span class="sub-indicator-name"> Plăți Luna</span>
<span class="sub-indicator-desc">Plăți luna curentă</span>
<span class="sub-indicator-value">{{ formatCurrency(data.cash_flow.plati_luna.value) }}</span>
</div>
<div v-if="data.cash_flow.cf_an_precedent" class="sub-indicator-row">
<span class="sub-indicator-name"> CF An Precedent</span>
<span class="sub-indicator-desc">Cash Flow YTD an precedent</span>
<span class="sub-indicator-value">{{ formatCurrency(data.cash_flow.cf_an_precedent.value) }}</span>
</div>
<div v-if="data.cash_flow.datorii_restante" class="sub-indicator-row">
<span class="sub-indicator-name"> Datorii Restante</span>
<span class="sub-indicator-desc">Datorii cu scadență depășită</span>
<span class="sub-indicator-value">{{ formatCurrency(data.cash_flow.datorii_restante.value) }}</span>
</div>
</template>
</template>
<!-- Section 5: Dinamică -->
<h3 v-if="data.dinamica" class="section-heading section-heading-toggle" @click="toggleSubSection('dinamica')">
Dinamică
<i class="pi" :class="subSectionsExpanded.dinamica ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.dinamica">
<IndicatorItem
v-if="data.dinamica.crestere_vanzari_yoy"
label="Creștere Vânzări YoY"
:value="data.dinamica.crestere_vanzari_yoy.value"
:status="data.dinamica.crestere_vanzari_yoy.status"
:sparkline-data="data.dinamica.crestere_vanzari_yoy.sparkline_data || []"
:sparkline-labels="data.dinamica.crestere_vanzari_yoy.sparkline_labels || []"
:thresholds="data.dinamica.crestere_vanzari_yoy"
:decimals="0"
description="Trendul vânzărilor față de anul trecut. (Vânz curent - Vânz anterior) / Vânz anterior × 100 • Ideal: >5%"
/>
<IndicatorItem
v-if="data.dinamica.crestere_achizitii_yoy"
label="Creștere Achiziții YoY"
:value="data.dinamica.crestere_achizitii_yoy.value"
:status="data.dinamica.crestere_achizitii_yoy.status"
:sparkline-data="data.dinamica.crestere_achizitii_yoy.sparkline_data || []"
:sparkline-labels="data.dinamica.crestere_achizitii_yoy.sparkline_labels || []"
:thresholds="data.dinamica.crestere_achizitii_yoy"
:decimals="0"
description="Trendul achizițiilor față de anul trecut. (Ach curent - Ach anterior) / Ach anterior × 100 • Corelat cu vânzări"
/>
<IndicatorItem
v-if="data.dinamica.marja_implicita"
label="Marjă Implicită"
:value="data.dinamica.marja_implicita.value"
:status="data.dinamica.marja_implicita.status"
:sparkline-data="data.dinamica.marja_implicita.sparkline_data || []"
:sparkline-labels="data.dinamica.marja_implicita.sparkline_labels || []"
:thresholds="data.dinamica.marja_implicita"
:decimals="0"
description="Profitabilitatea brută estimată. (Vânzări - Achiziții) / Vânzări × 100 • Ideal: >20%"
/>
<template v-if="subSectionsExpanded.dinamica">
<div v-if="data.dinamica.vanzari_an_curent" class="sub-indicator-row">
<span class="sub-indicator-name"> Cifra de Afaceri An Curent</span>
<span class="sub-indicator-desc">Clasa 7 (70x) fără TVA</span>
<span class="sub-indicator-value">{{ formatCurrency(data.dinamica.vanzari_an_curent.value) }}</span>
</div>
<div v-if="data.dinamica.vanzari_an_precedent" class="sub-indicator-row">
<span class="sub-indicator-name"> Cifra de Afaceri An Precedent</span>
<span class="sub-indicator-desc">Clasa 7 (70x) fără TVA</span>
<span class="sub-indicator-value">{{ formatCurrency(data.dinamica.vanzari_an_precedent.value) }}</span>
</div>
<div v-if="data.dinamica.achizitii_an_curent" class="sub-indicator-row">
<span class="sub-indicator-name"> Achiziții Totale An Curent</span>
<span class="sub-indicator-desc">Stocuri + cheltuieli directe, fără TVA</span>
<span class="sub-indicator-value">{{ formatCurrency(data.dinamica.achizitii_an_curent.value) }}</span>
</div>
<div v-if="data.dinamica.achizitii_an_precedent" class="sub-indicator-row">
<span class="sub-indicator-name"> Achiziții Totale An Precedent</span>
<span class="sub-indicator-desc">Stocuri + cheltuieli directe, fără TVA</span>
<span class="sub-indicator-value">{{ formatCurrency(data.dinamica.achizitii_an_precedent.value) }}</span>
</div>
</template>
</template>
<!-- Section 6: Profitabilitate -->
<h3 v-if="data.profitabilitate" class="section-heading section-heading-toggle" @click="toggleSubSection('profitabilitate')">
Profitabilitate
<i class="pi" :class="subSectionsExpanded.profitabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.profitabilitate">
<IndicatorItem
v-if="data.profitabilitate.cifra_afaceri"
label="Cifra de Afaceri"
:value="data.profitabilitate.cifra_afaceri.value"
:status="data.profitabilitate.cifra_afaceri.status"
:sparkline-data="data.profitabilitate.cifra_afaceri.sparkline_data || []"
:sparkline-labels="data.profitabilitate.cifra_afaceri.sparkline_labels || []"
:thresholds="data.profitabilitate.cifra_afaceri"
:decimals="0"
description="Total venituri din activitatea operațională. Clasa 70x • Ideal: în creștere"
/>
<IndicatorItem
v-if="data.profitabilitate.cheltuieli_totale"
label="Cheltuieli Totale"
:value="data.profitabilitate.cheltuieli_totale.value"
:status="data.profitabilitate.cheltuieli_totale.status"
:sparkline-data="data.profitabilitate.cheltuieli_totale.sparkline_data || []"
:sparkline-labels="data.profitabilitate.cheltuieli_totale.sparkline_labels || []"
:thresholds="data.profitabilitate.cheltuieli_totale"
:decimals="0"
description="Operaționale + Financiare. Clasa 60x-68x"
/>
<IndicatorItem
v-if="data.profitabilitate.profit_brut"
label="Profit Brut (EBIT)"
:value="data.profitabilitate.profit_brut.value"
:status="data.profitabilitate.profit_brut.status"
:sparkline-data="data.profitabilitate.profit_brut.sparkline_data || []"
:sparkline-labels="data.profitabilitate.profit_brut.sparkline_labels || []"
:thresholds="data.profitabilitate.profit_brut"
:decimals="0"
description="Venituri - Cheltuieli operaționale • Ideal: pozitiv"
/>
<IndicatorItem
v-if="data.profitabilitate.marja_profit_brut"
label="Marja Profit"
:value="data.profitabilitate.marja_profit_brut.value"
:status="data.profitabilitate.marja_profit_brut.status"
:sparkline-data="data.profitabilitate.marja_profit_brut.sparkline_data || []"
:sparkline-labels="data.profitabilitate.marja_profit_brut.sparkline_labels || []"
:thresholds="data.profitabilitate.marja_profit_brut"
:decimals="0"
description="Procentul de profit din vânzări. Profit / Venituri × 100 • Ideal: >10%"
/>
<IndicatorItem
v-if="data.profitabilitate.roa"
label="Randament Active (ROA)"
:value="data.profitabilitate.roa.value"
:status="data.profitabilitate.roa.status"
:sparkline-data="data.profitabilitate.roa.sparkline_data || []"
:sparkline-labels="data.profitabilitate.roa.sparkline_labels || []"
:thresholds="data.profitabilitate.roa"
:decimals="0"
description="Profit / Active Totale × 100 • Ideal: >5%"
/>
<IndicatorItem
v-if="data.profitabilitate.roe"
label="Randament Capitaluri (ROE)"
:value="data.profitabilitate.roe.value"
:status="data.profitabilitate.roe.status"
:sparkline-data="data.profitabilitate.roe.sparkline_data || []"
:sparkline-labels="data.profitabilitate.roe.sparkline_labels || []"
:thresholds="data.profitabilitate.roe"
:decimals="0"
description="Profit / Capital Propriu × 100 • Ideal: >10%"
/>
<template v-if="subSectionsExpanded.profitabilitate">
<div v-if="data.profitabilitate.cheltuieli_operationale" class="sub-indicator-row">
<span class="sub-indicator-name"> Cheltuieli Operaționale</span>
<span class="sub-indicator-desc">Clasa 60x-65x + 68x (fără dobânzi 66x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.profitabilitate.cheltuieli_operationale.value) }}</span>
</div>
<div v-if="data.profitabilitate.cheltuieli_financiare" class="sub-indicator-row">
<span class="sub-indicator-name"> Cheltuieli Financiare</span>
<span class="sub-indicator-desc">Clasa 66x. Dobânzi, diferențe de curs valutar, etc.</span>
<span class="sub-indicator-value">{{ formatCurrency(data.profitabilitate.cheltuieli_financiare.value) }}</span>
</div>
<div v-if="data.profitabilitate.venituri" class="sub-indicator-row">
<span class="sub-indicator-name"> Venituri (Clasa 7)</span>
<span class="sub-indicator-desc">Total venituri pentru verificare calcul EBIT</span>
<span class="sub-indicator-value">{{ formatCurrency(data.profitabilitate.venituri.value) }}</span>
</div>
<div v-if="data.profitabilitate.active_totale" class="sub-indicator-row">
<span class="sub-indicator-name"> Active Totale</span>
<span class="sub-indicator-desc">Baza calcul ROA. Active Imobilizate + Active Curente</span>
<span class="sub-indicator-value">{{ formatCurrency(data.profitabilitate.active_totale.value) }}</span>
</div>
<div v-if="data.profitabilitate.capitaluri_proprii" class="sub-indicator-row">
<span class="sub-indicator-name"> Capitaluri Proprii</span>
<span class="sub-indicator-desc">Baza calcul ROE. Capital Social (101-106) + Rezultat (117, 121)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.profitabilitate.capitaluri_proprii.value) }}</span>
</div>
</template>
</template>
<!-- Section 7: Altman Z-Score -->
<h3 v-if="data.altman_zscore" class="section-heading section-heading-toggle" @click="toggleSubSection('altman')">
Altman Z-Score
<i class="pi" :class="subSectionsExpanded.altman ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.altman_zscore">
<IndicatorItem
v-if="data.altman_zscore.zscore"
label="Z-Score"
:value="data.altman_zscore.zscore.value"
:status="data.altman_zscore.zscore.status"
:sparkline-data="data.altman_zscore.zscore.sparkline_data || []"
:sparkline-labels="data.altman_zscore.zscore.sparkline_labels || []"
:thresholds="data.altman_zscore.zscore"
:decimals="0"
description="Scor de sănătate financiară. 6.56×X1 + 3.26×X2 + 6.72×X3 + 1.05×X4 • >2.6 = sigur, 1.1-2.6 = gri, <1.1 = risc"
/>
<template v-if="subSectionsExpanded.altman">
<div v-if="data.altman_zscore.x1" class="sub-indicator-row">
<span class="sub-indicator-name"> X1 (Lichiditate)</span>
<span class="sub-indicator-desc">Capital de lucru / Active totale. Măsoară lichiditatea pe termen scurt.</span>
<span class="sub-indicator-value">{{ formatNumber(data.altman_zscore.x1.value, 2) }}</span>
</div>
<div v-if="data.altman_zscore.x2" class="sub-indicator-row">
<span class="sub-indicator-name"> X2 (Profitabilitate)</span>
<span class="sub-indicator-desc">Rezultat reportat / Active totale. Măsoară profiturile reinvestite.</span>
<span class="sub-indicator-value">{{ formatNumber(data.altman_zscore.x2.value, 2) }}</span>
</div>
<div v-if="data.altman_zscore.x3" class="sub-indicator-row">
<span class="sub-indicator-name"> X3 (Eficiență)</span>
<span class="sub-indicator-desc">EBIT / Active totale. Măsoară productivitatea activelor.</span>
<span class="sub-indicator-value">{{ formatNumber(data.altman_zscore.x3.value, 2) }}</span>
</div>
<div v-if="data.altman_zscore.x4" class="sub-indicator-row">
<span class="sub-indicator-name"> X4 (Solvabilitate)</span>
<span class="sub-indicator-desc">Capital propriu / Datorii totale. Măsoară gradul de îndatorare.</span>
<span class="sub-indicator-value">{{ formatNumber(data.altman_zscore.x4.value, 2) }}</span>
</div>
<div v-if="data.altman_zscore.capital_de_lucru" class="sub-indicator-row">
<span class="sub-indicator-name"> Capital de Lucru</span>
<span class="sub-indicator-desc">Active Curente - Datorii Curente (3xx+4xx activ+5xx) - (401,404,4xx pasiv)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.altman_zscore.capital_de_lucru.value) }}</span>
</div>
<div v-if="data.altman_zscore.active_totale" class="sub-indicator-row">
<span class="sub-indicator-name"> Active Totale</span>
<span class="sub-indicator-desc">Active Imobilizate (2xx net) + Active Curente (3xx+4xx activ+5xx)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.altman_zscore.active_totale.value) }}</span>
</div>
<div v-if="data.altman_zscore.datorii_totale" class="sub-indicator-row">
<span class="sub-indicator-name"> Datorii Totale</span>
<span class="sub-indicator-desc">Datorii Curente (401,404,4xx pasiv) + Datorii Termen Lung (16x)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.altman_zscore.datorii_totale.value) }}</span>
</div>
</template>
</template>
<!-- Section 8: Solvabilitate -->
<h3 v-if="data.solvabilitate" class="section-heading section-heading-toggle" @click="toggleSubSection('solvabilitate')">
Solvabilitate
<i class="pi" :class="subSectionsExpanded.solvabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
</h3>
<template v-if="data.solvabilitate">
<IndicatorItem
v-if="data.solvabilitate.activ_net_contabil"
label="Activ Net Contabil (ANC)"
:value="data.solvabilitate.activ_net_contabil.value"
:status="data.solvabilitate.activ_net_contabil.status"
:sparkline-data="data.solvabilitate.activ_net_contabil.sparkline_data || []"
:sparkline-labels="data.solvabilitate.activ_net_contabil.sparkline_labels || []"
:thresholds="data.solvabilitate.activ_net_contabil"
:decimals="0"
description="Total Active - Total Datorii. Valoarea netă a firmei. • Ideal: pozitiv"
/>
<IndicatorItem
v-if="data.solvabilitate.rata_anc_capital"
label="Rata ANC / Capital Social"
:value="data.solvabilitate.rata_anc_capital.value"
:status="data.solvabilitate.rata_anc_capital.status"
:sparkline-data="data.solvabilitate.rata_anc_capital.sparkline_data || []"
:sparkline-labels="data.solvabilitate.rata_anc_capital.sparkline_labels || []"
:thresholds="data.solvabilitate.rata_anc_capital"
:decimals="0"
description="ANC / Capital Social × 100. Sub 50% → restricții legale (din 2026). • Ideal: ≥100%"
/>
<template v-if="subSectionsExpanded.solvabilitate">
<div v-if="data.solvabilitate.total_active" class="sub-indicator-row">
<span class="sub-indicator-name"> Total Active</span>
<span class="sub-indicator-desc">Active Imobilizate + Active Curente (baza de calcul ANC)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.solvabilitate.total_active.value) }}</span>
</div>
<div v-if="data.solvabilitate.total_datorii" class="sub-indicator-row">
<span class="sub-indicator-name"> Total Datorii</span>
<span class="sub-indicator-desc">Datorii Curente + Datorii pe Termen Lung (se scade din active)</span>
<span class="sub-indicator-value">{{ formatCurrency(data.solvabilitate.total_datorii.value) }}</span>
</div>
<div v-if="data.solvabilitate.capital_social" class="sub-indicator-row">
<span class="sub-indicator-name"> Capital Social</span>
<span class="sub-indicator-desc">Capital subscris și vărsat (cont 101) - baza calcul Rata ANC</span>
<span class="sub-indicator-value">{{ formatCurrency(data.solvabilitate.capital_social.value) }}</span>
</div>
</template>
</template>
</div>
</template>
</Card>
</div>
<!-- Mobile Bottom Navigation -->
<MobileBottomNav v-if="isMobile" />
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
import { useRouter } from "vue-router";
import { useDashboardStore } from "@reports/stores/dashboard";
import {
useCompanyStore,
useAccountingPeriodStore,
useAuthStore,
} from "@reports/stores/sharedStores";
import MobileTopBar from "@shared/components/mobile/MobileTopBar.vue";
import MobileBottomNav from "@shared/components/mobile/MobileBottomNav.vue";
import MobileDrawerMenu from "@shared/components/mobile/MobileDrawerMenu.vue";
import IndicatorItem from "@reports/components/dashboard/cards/IndicatorItem.vue";
// Stores
const dashboardStore = useDashboardStore();
const companyStore = useCompanyStore();
const periodStore = useAccountingPeriodStore();
const authStore = useAuthStore();
const router = useRouter();
// Mobile detection
const isMobile = ref(window.innerWidth <= 768);
// UI state
const showDrawer = ref(false);
// Track current period for this view (luna/an selected for display)
const currentLuna = ref(null);
const currentAn = ref(null);
// Period selection key ("luna-an" format)
const desktopSelectedPeriodKey = ref(null);
// Derived data from store
const loading = computed(() => dashboardStore.financialIndicators.loading);
const error = computed(() => dashboardStore.financialIndicators.error);
const data = computed(() => dashboardStore.financialIndicators.data);
// Period options with unified key format "luna-an"
const periodDropdownOptions = computed(() =>
(periodStore.periods || []).map((p) => ({
key: `${p.luna}-${p.an}`,
label: p.display_name,
luna: p.luna,
an: p.an,
}))
);
// Sub-sections expand/collapse state (all collapsed by default)
const subSectionsExpanded = ref({
lichiditate: false,
eficienta: false,
risc: false,
cash_flow: false,
dinamica: false,
profitabilitate: false,
altman: false,
solvabilitate: false,
});
const toggleSubSection = (key) => {
subSectionsExpanded.value[key] = !subSectionsExpanded.value[key];
};
// Compute default period: last closed month (current period - 1)
const defaultPeriod = computed(() => {
if (!periodStore.selectedPeriod) return null;
const { luna, an } = periodStore.selectedPeriod;
if (luna === 1) return { luna: 12, an: an - 1 };
return { luna: luna - 1, an };
});
// Top bar actions for mobile
const topBarActions = computed(() => [
{
icon: "pi pi-refresh",
label: "Actualizează",
tooltip: "Actualizează",
},
]);
const handleTopBarAction = (action) => {
if (action.icon === "pi pi-refresh") {
loadData();
}
};
// Period change handler (used by the single period dropdown)
const onDesktopPeriodChange = () => {
if (desktopSelectedPeriodKey.value) {
const option = periodDropdownOptions.value.find(
(p) => p.key === desktopSelectedPeriodKey.value
);
if (option) {
currentLuna.value = option.luna;
currentAn.value = option.an;
loadData();
}
}
};
// Load indicator data from API
const loadData = async () => {
if (!companyStore.selectedCompany) return;
const luna = currentLuna.value;
const an = currentAn.value;
await dashboardStore.loadFinancialIndicators(
companyStore.selectedCompany.id_firma,
luna,
an
);
};
// Format a monetary value (Romanian locale, no decimals, thousands separators)
const formatCurrency = (amount) => {
if (amount === null || amount === undefined) return "-";
return new Intl.NumberFormat("ro-RO", {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
};
// Format a ratio / generic number with custom decimal places
const formatNumber = (value, decimals = 2) => {
if (value === null || value === undefined) return "-";
return new Intl.NumberFormat("ro-RO", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
};
// Handle logout
const handleLogout = async () => {
await authStore.logout();
router.push("/login");
};
// Handle server switch
const handleServerSwitched = async () => {
companyStore.clearSelectedCompany();
periodStore.reset();
await companyStore.loadCompanies();
if (companyStore.selectedCompany?.id_firma) {
await periodStore.loadPeriods(companyStore.selectedCompany.id_firma);
}
};
// Handle window resize
const handleResize = () => {
isMobile.value = window.innerWidth <= 768;
};
// Lifecycle
onMounted(async () => {
window.addEventListener("resize", handleResize);
// Load companies if not loaded
if (!companyStore.hasCompanies) {
await companyStore.loadCompanies();
}
// Load periods if not already loaded (needed for dropdown options)
if (companyStore.selectedCompany?.id_firma && !periodStore.periods?.length) {
await periodStore.loadPeriods(companyStore.selectedCompany.id_firma);
}
// Initialize period to default (last closed month)
if (defaultPeriod.value) {
currentLuna.value = defaultPeriod.value.luna;
currentAn.value = defaultPeriod.value.an;
// Sync period dropdown with "luna-an" key format
desktopSelectedPeriodKey.value = `${defaultPeriod.value.luna}-${defaultPeriod.value.an}`;
}
// Use cached data if available, otherwise load
if (!dashboardStore.financialIndicators.data) {
await loadData();
}
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
});
// Watch for company changes
watch(
() => companyStore.selectedCompany,
async (newCompany) => {
if (newCompany) {
await loadData();
}
}
);
// Watch for global period changes - update default period accordingly
watch(
() => periodStore.selectedPeriod,
(newPeriod) => {
if (newPeriod && !dashboardStore.financialIndicators.data) {
const adjusted =
newPeriod.luna === 1
? { luna: 12, an: newPeriod.an - 1 }
: { luna: newPeriod.luna - 1, an: newPeriod.an };
currentLuna.value = adjusted.luna;
currentAn.value = adjusted.an;
desktopSelectedPeriodKey.value = `${adjusted.luna}-${adjusted.an}`;
}
}
);
</script>
<style scoped>
/* Page Container */
.indicators-view {
max-width: 1400px;
margin: 0 auto;
padding: var(--space-xl);
}
/* Mobile layout: account for fixed top/bottom bars */
.mobile-layout .indicators-view {
padding-top: calc(56px + var(--space-md));
padding-bottom: calc(56px + var(--space-md));
padding-left: var(--space-md);
padding-right: var(--space-md);
}
/* Period filter bar - compact inline row */
.period-filter-bar {
display: flex;
align-items: center;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
.period-filter-bar .period-dropdown {
flex: 1;
max-width: 280px;
}
.period-filter-bar .refresh-btn {
flex-shrink: 0;
}
/* Card spacing */
.state-card,
.indicators-card {
margin-bottom: var(--space-md);
}
/* Loading / Error / Empty states */
.loading-state,
.error-state,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-md);
padding: var(--space-xl);
text-align: center;
color: var(--text-color-secondary);
}
.error-icon,
.empty-icon {
font-size: var(--text-3xl);
}
.error-icon {
color: var(--red-600);
}
.error-message {
color: var(--text-color);
font-size: var(--text-base);
}
/* Section heading */
.section-heading {
font-size: var(--text-base);
font-weight: var(--font-semibold);
color: var(--text-color);
padding: var(--space-sm) 0;
border-bottom: 2px solid var(--surface-border);
margin-top: var(--space-lg);
margin-bottom: 0;
}
.section-heading:first-child {
margin-top: 0;
}
/* Clickable section heading with chevron toggle */
.section-heading-toggle {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
}
.section-heading-toggle:hover {
color: var(--color-primary);
}
.section-heading-toggle .pi {
font-size: var(--text-sm);
color: var(--text-color-secondary);
transition: color 0.15s;
}
.section-heading-toggle:hover .pi {
color: var(--color-primary);
}
/* Sub-indicator rows (for breakdown values) */
.sub-indicator-row {
display: flex;
align-items: center;
padding: var(--space-xs) 0 var(--space-xs) var(--space-md);
border-bottom: 1px solid var(--surface-border);
gap: var(--space-sm);
min-height: 36px;
}
.sub-indicator-row:last-child {
border-bottom: none;
}
.sub-indicator-name {
flex: 1;
font-size: var(--text-sm);
color: var(--text-color-secondary);
min-width: 120px;
}
.sub-indicator-desc {
flex: 2;
font-size: var(--text-xs);
color: var(--text-color-secondary);
font-style: italic;
}
.sub-indicator-value {
font-size: var(--text-sm);
font-weight: var(--font-medium);
font-variant-numeric: tabular-nums;
color: var(--text-color);
white-space: nowrap;
}
/* Dark mode for error icon */
[data-theme="dark"] .error-icon {
color: var(--red-400);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .error-icon {
color: var(--red-400);
}
}
/* Responsive */
@media (max-width: 768px) {
.indicators-view {
padding: var(--space-md);
}
.period-filter-bar .period-dropdown {
max-width: none;
}
}
</style>