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>
1086 lines
50 KiB
Vue
1086 lines
50 KiB
Vue
<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>
|