feat(dashboard): add datorat/achitat/sold breakdown for budget debts
Replaces "luna prec / luna curentă" columns with a clearer accounting reconciliation flow: Datorat (owed) → Achitat (paid) → Sold (remaining). Backend models gain 3 new fields per sub-account and group. Frontend shows ✓ when a debt is fully cleared. Mobile TVA card now shows both total and remaining sold. SwipeableCards gains fixedDots/fillHeight props for better layout above MobileBottomNav. Telegram formatter updated to use new fields and drops redundant RON suffix from amounts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@ class BudgetDebtSubAccount(BaseModel):
|
|||||||
label: str # ex: "4311 - CAS angajat"
|
label: str # ex: "4311 - CAS angajat"
|
||||||
precedent: Decimal # sold luna precedentă (pozitiv=datorie, negativ=creanță)
|
precedent: Decimal # sold luna precedentă (pozitiv=datorie, negativ=creanță)
|
||||||
curent: Decimal # sold luna curentă (pozitiv=datorie, negativ=creanță)
|
curent: Decimal # sold luna curentă (pozitiv=datorie, negativ=creanță)
|
||||||
|
datorat: Decimal = Decimal('0') # datorie din luna precedentă (= preccred - precdeb)
|
||||||
|
achitat: Decimal = Decimal('0') # plăți efectuate luna curentă (= ruldeb)
|
||||||
|
sold: Decimal = Decimal('0') # sold final real (= soldcred - solddeb)
|
||||||
|
|
||||||
class BudgetDebtGroup(BaseModel):
|
class BudgetDebtGroup(BaseModel):
|
||||||
"""Grup de datorii la buget (TVA / BASS / CAM)"""
|
"""Grup de datorii la buget (TVA / BASS / CAM)"""
|
||||||
@@ -17,6 +20,9 @@ class BudgetDebtGroup(BaseModel):
|
|||||||
precedent: Decimal # total grup luna prec (semn ±)
|
precedent: Decimal # total grup luna prec (semn ±)
|
||||||
curent: Decimal # total grup luna crt (semn ±)
|
curent: Decimal # total grup luna crt (semn ±)
|
||||||
sub_accounts: List[BudgetDebtSubAccount] = []
|
sub_accounts: List[BudgetDebtSubAccount] = []
|
||||||
|
datorat: Decimal = Decimal('0') # total datorie grup luna precedentă
|
||||||
|
achitat: Decimal = Decimal('0') # total plăți grup luna curentă
|
||||||
|
sold: Decimal = Decimal('0') # sold final real al grupului
|
||||||
|
|
||||||
class TreasuryAccount(BaseModel):
|
class TreasuryAccount(BaseModel):
|
||||||
"""Cont de trezorerie (bancă/casă)"""
|
"""Cont de trezorerie (bancă/casă)"""
|
||||||
@@ -146,4 +152,5 @@ class DashboardSummary(BaseModel):
|
|||||||
|
|
||||||
# DATORII LA BUGET - breakdown pe grupe (TVA / BASS / CAM) cu sub-conturi
|
# DATORII LA BUGET - breakdown pe grupe (TVA / BASS / CAM) cu sub-conturi
|
||||||
budget_debt_breakdown: List[BudgetDebtGroup] = []
|
budget_debt_breakdown: List[BudgetDebtGroup] = []
|
||||||
budget_debt_total_precedent: Decimal = Decimal('0') # suma tuturor grupurilor luna prec
|
budget_debt_total_precedent: Decimal = Decimal('0') # suma tuturor grupurilor luna prec
|
||||||
|
budget_debt_total_sold: Decimal = Decimal('0') # sold final total (cât mai rămâne de plată)
|
||||||
@@ -555,6 +555,9 @@ class DashboardService:
|
|||||||
sub_accounts = []
|
sub_accounts = []
|
||||||
group_prec = Decimal('0')
|
group_prec = Decimal('0')
|
||||||
group_cur = Decimal('0')
|
group_cur = Decimal('0')
|
||||||
|
group_datorat = Decimal('0')
|
||||||
|
group_achitat = Decimal('0')
|
||||||
|
group_sold = Decimal('0')
|
||||||
|
|
||||||
if group_def['key'] == 'TVA':
|
if group_def['key'] == 'TVA':
|
||||||
# TVA special: valorile deja calculate (semn ±)
|
# TVA special: valorile deja calculate (semn ±)
|
||||||
@@ -562,22 +565,39 @@ class DashboardService:
|
|||||||
tva_cur = tva_plata_curent - tva_recuperat_curent
|
tva_cur = tva_plata_curent - tva_recuperat_curent
|
||||||
group_prec = tva_prec
|
group_prec = tva_prec
|
||||||
group_cur = tva_cur
|
group_cur = tva_cur
|
||||||
|
|
||||||
|
# Achitat TVA = plăți efective din luna curentă (ruldeb pe contul de TVA plată)
|
||||||
|
if sold_4423 > 0:
|
||||||
|
tva_achitat_prec = tva_data['4423']['ruldeb']
|
||||||
|
else:
|
||||||
|
tva_achitat_prec = tva_data['4427']['ruldeb']
|
||||||
|
group_datorat = tva_prec
|
||||||
|
group_achitat = tva_achitat_prec
|
||||||
|
group_sold = max(Decimal('0'), tva_prec - tva_achitat_prec)
|
||||||
|
|
||||||
# Sub-conturi TVA (doar cele cu sold non-zero)
|
# Sub-conturi TVA (doar cele cu sold non-zero)
|
||||||
for cont in ['4423', '4424', '4426', '4427']:
|
for cont in ['4423', '4424', '4426', '4427']:
|
||||||
if cont in all_budget_data:
|
if cont in all_budget_data:
|
||||||
d = all_budget_data[cont]
|
d = all_budget_data[cont]
|
||||||
if cont == '4424': # creanță → semn negativ
|
if cont == '4424': # creanță → semn negativ
|
||||||
val_prec = -(d['precdeb'] - d['preccred'])
|
val_prec = -(d['precdeb'] - d['preccred'])
|
||||||
val_cur = -(d['ruldeb'] - d['rulcred'])
|
val_cur = -(d['rulcred'])
|
||||||
else: # 4423, 4426, 4427: rulcred - ruldeb (lunar, nu cumulat)
|
val_sold = Decimal('0') # creanță: nu are sold de plată
|
||||||
|
else: # 4423, 4426, 4427
|
||||||
val_prec = d['preccred'] - d['precdeb']
|
val_prec = d['preccred'] - d['precdeb']
|
||||||
val_cur = d['rulcred'] - d['ruldeb']
|
val_cur = d['rulcred'] # obligații noi luna curentă
|
||||||
|
val_sold = max(Decimal('0'), val_prec - d['ruldeb'])
|
||||||
|
val_datorat = val_prec
|
||||||
|
val_achitat = d['ruldeb']
|
||||||
if val_prec != 0 or val_cur != 0:
|
if val_prec != 0 or val_cur != 0:
|
||||||
sub_accounts.append(BudgetDebtSubAccount(
|
sub_accounts.append(BudgetDebtSubAccount(
|
||||||
cont=cont,
|
cont=cont,
|
||||||
label=ACCOUNT_LABELS[cont],
|
label=ACCOUNT_LABELS[cont],
|
||||||
precedent=val_prec,
|
precedent=val_prec,
|
||||||
curent=val_cur,
|
curent=val_cur,
|
||||||
|
datorat=val_datorat,
|
||||||
|
achitat=val_achitat,
|
||||||
|
sold=val_sold,
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
# BASS și CAM: matching exact pe codul contului
|
# BASS și CAM: matching exact pe codul contului
|
||||||
@@ -585,16 +605,25 @@ class DashboardService:
|
|||||||
if account_code in all_budget_data:
|
if account_code in all_budget_data:
|
||||||
d = all_budget_data[account_code]
|
d = all_budget_data[account_code]
|
||||||
val_prec = d['preccred'] - d['precdeb'] # pozitiv = datorie
|
val_prec = d['preccred'] - d['precdeb'] # pozitiv = datorie
|
||||||
val_cur = d['rulcred'] - d['ruldeb'] # rulaj luna curentă (consistent cu TVA)
|
val_cur = d['rulcred'] # obligații noi luna curentă
|
||||||
|
val_datorat = val_prec
|
||||||
|
val_achitat = d['ruldeb'] # plăți directe (debit)
|
||||||
|
val_sold = max(Decimal('0'), val_datorat - val_achitat) # datorat - achitat
|
||||||
if val_prec != 0 or val_cur != 0:
|
if val_prec != 0 or val_cur != 0:
|
||||||
sub_accounts.append(BudgetDebtSubAccount(
|
sub_accounts.append(BudgetDebtSubAccount(
|
||||||
cont=account_code,
|
cont=account_code,
|
||||||
label=ACCOUNT_LABELS.get(account_code, account_code),
|
label=ACCOUNT_LABELS.get(account_code, account_code),
|
||||||
precedent=val_prec,
|
precedent=val_prec,
|
||||||
curent=val_cur,
|
curent=val_cur,
|
||||||
|
datorat=val_datorat,
|
||||||
|
achitat=val_achitat,
|
||||||
|
sold=val_sold,
|
||||||
))
|
))
|
||||||
group_prec += val_prec
|
group_prec += val_prec
|
||||||
group_cur += val_cur
|
group_cur += val_cur
|
||||||
|
group_datorat += val_datorat
|
||||||
|
group_achitat += val_achitat
|
||||||
|
group_sold += val_sold
|
||||||
|
|
||||||
if group_prec != 0 or group_cur != 0 or sub_accounts:
|
if group_prec != 0 or group_cur != 0 or sub_accounts:
|
||||||
budget_debt_breakdown.append(BudgetDebtGroup(
|
budget_debt_breakdown.append(BudgetDebtGroup(
|
||||||
@@ -603,9 +632,13 @@ class DashboardService:
|
|||||||
precedent=group_prec,
|
precedent=group_prec,
|
||||||
curent=group_cur,
|
curent=group_cur,
|
||||||
sub_accounts=sub_accounts,
|
sub_accounts=sub_accounts,
|
||||||
|
datorat=group_datorat,
|
||||||
|
achitat=group_achitat,
|
||||||
|
sold=group_sold,
|
||||||
))
|
))
|
||||||
|
|
||||||
budget_debt_total_precedent = sum(g.precedent for g in budget_debt_breakdown)
|
budget_debt_total_precedent = sum(g.precedent for g in budget_debt_breakdown)
|
||||||
|
budget_debt_total_sold = sum(g.sold for g in budget_debt_breakdown)
|
||||||
|
|
||||||
# Procesare trezorerie
|
# Procesare trezorerie
|
||||||
treasury_accounts = []
|
treasury_accounts = []
|
||||||
@@ -697,6 +730,7 @@ class DashboardService:
|
|||||||
# Datorii la buget pe grupe cu sub-conturi
|
# Datorii la buget pe grupe cu sub-conturi
|
||||||
budget_debt_breakdown=budget_debt_breakdown,
|
budget_debt_breakdown=budget_debt_breakdown,
|
||||||
budget_debt_total_precedent=budget_debt_total_precedent,
|
budget_debt_total_precedent=budget_debt_total_precedent,
|
||||||
|
budget_debt_total_sold=budget_debt_total_sold,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ def format_dashboard_response(data: Dict[str, Any], company_name: str = None) ->
|
|||||||
# Sold total trezorerie (casa + banca) - rotunjit la leu
|
# Sold total trezorerie (casa + banca) - rotunjit la leu
|
||||||
treasury_totals = data.get('treasury_totals_by_currency', {})
|
treasury_totals = data.get('treasury_totals_by_currency', {})
|
||||||
sold_trezorerie = round(float(treasury_totals.get('RON', 0)))
|
sold_trezorerie = round(float(treasury_totals.get('RON', 0)))
|
||||||
text += f"**Sold Trezorerie:** {sold_trezorerie:,} RON\n\n"
|
text += f"**Sold Trezorerie:** {sold_trezorerie:,}\n\n"
|
||||||
|
|
||||||
# Sold Clienți - rotunjit la leu
|
# Sold Clienți - rotunjit la leu
|
||||||
clienti_sold = round(float(data.get('clienti_sold_total', 0)))
|
clienti_sold = round(float(data.get('clienti_sold_total', 0)))
|
||||||
clienti_in_termen = round(float(data.get('clienti_sold_in_termen', 0)))
|
clienti_in_termen = round(float(data.get('clienti_sold_in_termen', 0)))
|
||||||
clienti_restant = round(float(data.get('clienti_sold_restant', 0)))
|
clienti_restant = round(float(data.get('clienti_sold_restant', 0)))
|
||||||
|
|
||||||
text += f"**Sold Clienți:** {clienti_sold:,} RON\n"
|
text += f"**Sold Clienți:** {clienti_sold:,}\n"
|
||||||
text += f" - În termen: {clienti_in_termen:,} RON\n"
|
text += f" - În termen: {clienti_in_termen:,}\n"
|
||||||
text += f" - Restanță: {clienti_restant:,} RON\n\n"
|
text += f" - Restanță: {clienti_restant:,}\n\n"
|
||||||
|
|
||||||
# Sold Furnizori BRUT (pentru consistență cu detaliile) - rotunjit la leu
|
# Sold Furnizori BRUT (pentru consistență cu detaliile) - rotunjit la leu
|
||||||
furnizori_in_termen = round(float(data.get('furnizori_sold_in_termen', 0)))
|
furnizori_in_termen = round(float(data.get('furnizori_sold_in_termen', 0)))
|
||||||
@@ -36,47 +36,40 @@ def format_dashboard_response(data: Dict[str, Any], company_name: str = None) ->
|
|||||||
furnizori_avansuri = round(float(data.get('furnizori_avansuri', 0)))
|
furnizori_avansuri = round(float(data.get('furnizori_avansuri', 0)))
|
||||||
furnizori_sold_net = round(float(data.get('furnizori_sold_total', 0)))
|
furnizori_sold_net = round(float(data.get('furnizori_sold_total', 0)))
|
||||||
|
|
||||||
text += f"**Sold Furnizori:** {furnizori_sold_brut:,} RON\n"
|
text += f"**Sold Furnizori:** {furnizori_sold_brut:,}\n"
|
||||||
text += f" - În termen: {furnizori_in_termen:,} RON\n"
|
text += f" - În termen: {furnizori_in_termen:,}\n"
|
||||||
text += f" - Restanță: {furnizori_restant:,} RON\n"
|
text += f" - Restanță: {furnizori_restant:,}\n"
|
||||||
if furnizori_avansuri != 0:
|
if furnizori_avansuri != 0:
|
||||||
text += f" - Avansuri: {furnizori_avansuri:,} RON\n"
|
text += f" - Avansuri: {furnizori_avansuri:,}\n"
|
||||||
text += f" - Net (după avansuri): {furnizori_sold_net:,} RON"
|
text += f" - Net (după avansuri): {furnizori_sold_net:,}"
|
||||||
else:
|
else:
|
||||||
text += f" - Net: {furnizori_sold_net:,} RON"
|
text += f" - Net: {furnizori_sold_net:,}"
|
||||||
|
|
||||||
# Solduri TVA - rotunjit la leu
|
# Datorii la Buget - două secțiuni: luna precedentă și luna curentă
|
||||||
tva_plata_prec = round(float(data.get('tva_plata_precedent', 0)))
|
|
||||||
tva_recup_prec = round(float(data.get('tva_recuperat_precedent', 0)))
|
|
||||||
tva_plata_cur = round(float(data.get('tva_plata_curent', 0)))
|
|
||||||
tva_recup_cur = round(float(data.get('tva_recuperat_curent', 0)))
|
|
||||||
|
|
||||||
# Afișează secțiunea doar dacă există cel puțin o valoare > 0
|
|
||||||
if tva_plata_prec > 0 or tva_recup_prec > 0 or tva_plata_cur > 0 or tva_recup_cur > 0:
|
|
||||||
text += "\n\n**Solduri TVA:**\n"
|
|
||||||
if tva_plata_prec > 0:
|
|
||||||
text += f" - TVA de plată precedent: {tva_plata_prec:,} RON\n"
|
|
||||||
if tva_recup_prec > 0:
|
|
||||||
text += f" - TVA de recuperat precedent: {tva_recup_prec:,} RON\n"
|
|
||||||
if tva_plata_cur > 0:
|
|
||||||
text += f" - TVA de plată curent: {tva_plata_cur:,} RON\n"
|
|
||||||
if tva_recup_cur > 0:
|
|
||||||
text += f" - TVA de recuperat curent: {tva_recup_cur:,} RON\n"
|
|
||||||
|
|
||||||
# Datorii la Buget - breakdown pe grupe (TVA / BASS / CAM), valori luna precedentă
|
|
||||||
budget_breakdown = data.get('budget_debt_breakdown', [])
|
budget_breakdown = data.get('budget_debt_breakdown', [])
|
||||||
if budget_breakdown:
|
if budget_breakdown:
|
||||||
grupe_cu_datorie = [
|
grupe_prec = [g for g in budget_breakdown if round(float(g.get('datorat', g.get('precedent', 0)))) > 0]
|
||||||
g for g in budget_breakdown
|
grupe_crt = [g for g in budget_breakdown if round(float(g.get('curent', 0))) > 0]
|
||||||
if round(float(g.get('precedent', 0))) > 0
|
|
||||||
]
|
if grupe_prec or grupe_crt:
|
||||||
if grupe_cu_datorie:
|
|
||||||
total_buget = sum(round(float(g.get('precedent', 0))) for g in grupe_cu_datorie)
|
|
||||||
text += "\n\n**Datorii la Buget:**\n"
|
text += "\n\n**Datorii la Buget:**\n"
|
||||||
for grupa in grupe_cu_datorie:
|
|
||||||
val = round(float(grupa.get('precedent', 0)))
|
if grupe_prec:
|
||||||
text += f" - {grupa.get('label', '')}: {val:,} RON\n"
|
total_sold = sum(round(float(g.get('sold', 0))) for g in grupe_prec)
|
||||||
text += f" Total: {total_buget:,} RON\n"
|
total_dat = sum(round(float(g.get('datorat', g.get('precedent', 0)))) for g in grupe_prec)
|
||||||
|
sold_total_str = f"{total_sold:,}" if total_sold > 0 else "0 \u2713"
|
||||||
|
text += f"\n _Precedent: dat: {total_dat:,}, sold: {sold_total_str}_\n"
|
||||||
|
for g in grupe_prec:
|
||||||
|
datorat = round(float(g.get('datorat', g.get('precedent', 0))))
|
||||||
|
sold = round(float(g.get('sold', 0)))
|
||||||
|
label = g.get('label', '')
|
||||||
|
sold_str = f"{sold:,}" if sold > 0 else "0 \u2713"
|
||||||
|
text += f" {label:<6} {datorat:,} · {sold_str}\n"
|
||||||
|
|
||||||
|
if grupe_crt:
|
||||||
|
items = [f"{g.get('label', '')} {round(float(g.get('curent', 0))):,}" for g in grupe_crt]
|
||||||
|
total_crt = sum(round(float(g.get('curent', 0))) for g in grupe_crt)
|
||||||
|
text += f"\n _Curent: {' \u00b7 '.join(items)} = {total_crt:,}_\n"
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
<i class="pi pi-chart-bar"></i>
|
|
||||||
Indicatori Financiari
|
Indicatori Financiari
|
||||||
</h3>
|
</h3>
|
||||||
<div class="period-selector-wrapper">
|
<div class="period-selector-wrapper">
|
||||||
@@ -102,7 +101,7 @@
|
|||||||
<!-- Lichiditate -->
|
<!-- Lichiditate -->
|
||||||
<div class="indicator-section" v-if="lichiditate">
|
<div class="indicator-section" v-if="lichiditate">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('lichiditate')">
|
<h4 class="section-title" @click="toggleSubIndicators('lichiditate')">
|
||||||
<span>💧 Lichiditate</span>
|
<span>Lichiditate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.lichiditate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.lichiditate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -178,7 +177,7 @@
|
|||||||
<!-- Eficiență -->
|
<!-- Eficiență -->
|
||||||
<div class="indicator-section" v-if="eficienta">
|
<div class="indicator-section" v-if="eficienta">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('eficienta')">
|
<h4 class="section-title" @click="toggleSubIndicators('eficienta')">
|
||||||
<span>⚡ Eficiență</span>
|
<span>Eficiență</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.eficienta ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.eficienta ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -290,7 +289,7 @@
|
|||||||
<!-- Risc -->
|
<!-- Risc -->
|
||||||
<div class="indicator-section" v-if="risc">
|
<div class="indicator-section" v-if="risc">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('risc')">
|
<h4 class="section-title" @click="toggleSubIndicators('risc')">
|
||||||
<span>⚠️ Risc</span>
|
<span>Risc</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.risc ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.risc ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -393,7 +392,7 @@
|
|||||||
<!-- Cash Flow -->
|
<!-- Cash Flow -->
|
||||||
<div class="indicator-section" v-if="cash_flow">
|
<div class="indicator-section" v-if="cash_flow">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('cashflow')">
|
<h4 class="section-title" @click="toggleSubIndicators('cashflow')">
|
||||||
<span>💰 Cash Flow</span>
|
<span>Cash Flow</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.cashflow ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.cashflow ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -478,7 +477,7 @@
|
|||||||
<!-- Dinamică -->
|
<!-- Dinamică -->
|
||||||
<div class="indicator-section" v-if="dinamica">
|
<div class="indicator-section" v-if="dinamica">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('dinamica')">
|
<h4 class="section-title" @click="toggleSubIndicators('dinamica')">
|
||||||
<span>📈 Dinamică</span>
|
<span>Dinamică</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.dinamica ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.dinamica ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -554,7 +553,7 @@
|
|||||||
<!-- Profitabilitate -->
|
<!-- Profitabilitate -->
|
||||||
<div class="indicator-section" v-if="profitabilitate">
|
<div class="indicator-section" v-if="profitabilitate">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('profitabilitate')">
|
<h4 class="section-title" @click="toggleSubIndicators('profitabilitate')">
|
||||||
<span>💹 Profitabilitate</span>
|
<span>Profitabilitate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.profitabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.profitabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -666,7 +665,7 @@
|
|||||||
<!-- Altman Z-Score -->
|
<!-- Altman Z-Score -->
|
||||||
<div class="indicator-section" v-if="altman_zscore">
|
<div class="indicator-section" v-if="altman_zscore">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('altman')">
|
<h4 class="section-title" @click="toggleSubIndicators('altman')">
|
||||||
<span>🎯 Altman Z-Score</span>
|
<span>Altman Z-Score</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.altman ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.altman ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -738,7 +737,7 @@
|
|||||||
<!-- Solvabilitate -->
|
<!-- Solvabilitate -->
|
||||||
<div class="indicator-section" v-if="solvabilitate">
|
<div class="indicator-section" v-if="solvabilitate">
|
||||||
<h4 class="section-title" @click="toggleSubIndicators('solvabilitate')">
|
<h4 class="section-title" @click="toggleSubIndicators('solvabilitate')">
|
||||||
<span>🏛️ Solvabilitate</span>
|
<span>Solvabilitate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.solvabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.solvabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="section-items">
|
<div class="section-items">
|
||||||
@@ -830,7 +829,7 @@
|
|||||||
<!-- Lichiditate -->
|
<!-- Lichiditate -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('lichiditate')">
|
<h4 class="category-title" @click="toggleSubIndicators('lichiditate')">
|
||||||
<span>💧 Lichiditate</span>
|
<span>Lichiditate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.lichiditate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.lichiditate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -898,7 +897,7 @@
|
|||||||
<!-- Eficiență -->
|
<!-- Eficiență -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('eficienta')">
|
<h4 class="category-title" @click="toggleSubIndicators('eficienta')">
|
||||||
<span>⚡ Eficiență</span>
|
<span>Eficiență</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.eficienta ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.eficienta ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -998,7 +997,7 @@
|
|||||||
<!-- Risc -->
|
<!-- Risc -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('risc')">
|
<h4 class="category-title" @click="toggleSubIndicators('risc')">
|
||||||
<span>⚠️ Risc</span>
|
<span>Risc</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.risc ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.risc ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1089,7 +1088,7 @@
|
|||||||
<!-- Cash Flow -->
|
<!-- Cash Flow -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('cashflow')">
|
<h4 class="category-title" @click="toggleSubIndicators('cashflow')">
|
||||||
<span>💰 Cash Flow</span>
|
<span>Cash Flow</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.cashflow ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.cashflow ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1166,7 +1165,7 @@
|
|||||||
<!-- Dinamică -->
|
<!-- Dinamică -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('dinamica')">
|
<h4 class="category-title" @click="toggleSubIndicators('dinamica')">
|
||||||
<span>📈 Dinamică</span>
|
<span>Dinamică</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.dinamica ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.dinamica ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1234,7 +1233,7 @@
|
|||||||
<!-- Altman Z-Score -->
|
<!-- Altman Z-Score -->
|
||||||
<div class="indicator-category">
|
<div class="indicator-category">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('altman')">
|
<h4 class="category-title" @click="toggleSubIndicators('altman')">
|
||||||
<span>🎯 Altman Z-Score</span>
|
<span>Altman Z-Score</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.altman ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.altman ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1305,7 +1304,7 @@
|
|||||||
<!-- Profitabilitate -->
|
<!-- Profitabilitate -->
|
||||||
<div class="indicator-category" v-if="profitabilitate">
|
<div class="indicator-category" v-if="profitabilitate">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('profitabilitate')">
|
<h4 class="category-title" @click="toggleSubIndicators('profitabilitate')">
|
||||||
<span>💰 Profitabilitate</span>
|
<span>Profitabilitate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.profitabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.profitabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1400,7 +1399,7 @@
|
|||||||
<!-- Solvabilitate -->
|
<!-- Solvabilitate -->
|
||||||
<div class="indicator-category" v-if="solvabilitate">
|
<div class="indicator-category" v-if="solvabilitate">
|
||||||
<h4 class="category-title" @click="toggleSubIndicators('solvabilitate')">
|
<h4 class="category-title" @click="toggleSubIndicators('solvabilitate')">
|
||||||
<span>🏛️ Solvabilitate</span>
|
<span>Solvabilitate</span>
|
||||||
<i class="pi" :class="subIndicatorsExpanded.solvabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
<i class="pi" :class="subIndicatorsExpanded.solvabilitate ? 'pi-chevron-up' : 'pi-chevron-down'"></i>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="category-items">
|
<div class="category-items">
|
||||||
@@ -1712,7 +1711,7 @@ const formatCurrency = (value) => {
|
|||||||
maximumFractionDigits: 0
|
maximumFractionDigits: 0
|
||||||
})
|
})
|
||||||
const sign = num < 0 ? '-' : num > 0 ? '+' : ''
|
const sign = num < 0 ? '-' : num > 0 ? '+' : ''
|
||||||
return `${sign}${formatted} RON`
|
return `${sign}${formatted}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize status for consistent styling
|
// Normalize status for consistent styling
|
||||||
@@ -1823,8 +1822,8 @@ const handlePeriodChange = () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Card Container */
|
/* Card Container */
|
||||||
.financial-indicators-card {
|
.financial-indicators-card {
|
||||||
background: var(--surface-card);
|
background: transparent;
|
||||||
border: 1px solid var(--surface-border);
|
border: none;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
padding: var(--space-md);
|
padding: var(--space-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1959,7 +1958,7 @@ const handlePeriodChange = () => {
|
|||||||
|
|
||||||
/* Each category section */
|
/* Each category section */
|
||||||
.indicator-section {
|
.indicator-section {
|
||||||
background: var(--surface-ground);
|
background: transparent;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
padding: var(--space-md);
|
padding: var(--space-md);
|
||||||
border: 1px solid var(--surface-border);
|
border: 1px solid var(--surface-border);
|
||||||
@@ -2253,7 +2252,6 @@ const handlePeriodChange = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-top: var(--space-sm);
|
padding-top: var(--space-sm);
|
||||||
border-top: 1px solid var(--surface-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-toggle-btn {
|
.expand-toggle-btn {
|
||||||
@@ -2388,7 +2386,7 @@ const handlePeriodChange = () => {
|
|||||||
================================================ */
|
================================================ */
|
||||||
.mobile-footer {
|
.mobile-footer {
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
background: var(--surface-ground);
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================
|
/* ================================================
|
||||||
@@ -2410,7 +2408,10 @@ const handlePeriodChange = () => {
|
|||||||
|
|
||||||
/* Category sections */
|
/* Category sections */
|
||||||
.indicator-category {
|
.indicator-category {
|
||||||
margin-bottom: var(--space-lg);
|
border: 1px solid var(--surface-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-title {
|
.category-title {
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ const handleMouseLeave = () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Indicator Item Container */
|
/* Indicator Item Container */
|
||||||
.indicator-item {
|
.indicator-item {
|
||||||
background: var(--surface-ground);
|
background: transparent;
|
||||||
border: 1px solid var(--surface-border);
|
border: 1px solid var(--surface-border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
padding: var(--space-md);
|
padding: var(--space-md);
|
||||||
|
|||||||
@@ -8,9 +8,18 @@
|
|||||||
<div class="solduri-compact-card__header">
|
<div class="solduri-compact-card__header">
|
||||||
<div class="solduri-compact-card__content">
|
<div class="solduri-compact-card__content">
|
||||||
<span class="solduri-compact-card__label">{{ label }}</span>
|
<span class="solduri-compact-card__label">{{ label }}</span>
|
||||||
<span class="solduri-compact-card__value" :class="valueColorClass">
|
<!-- type !== 'tva': simple value -->
|
||||||
|
<span v-if="type !== 'tva'" class="solduri-compact-card__value" :class="valueColorClass">
|
||||||
{{ formatAmount(total) }}
|
{{ formatAmount(total) }}
|
||||||
</span>
|
</span>
|
||||||
|
<!-- type === 'tva': datorat · sold on same line, same font -->
|
||||||
|
<div v-else class="solduri-compact-card__debt-line">
|
||||||
|
<span class="solduri-compact-card__value" :class="valueColorClass">{{ formatAmount(total) }}</span>
|
||||||
|
<span class="solduri-compact-card__debt-sep">·</span>
|
||||||
|
<span class="solduri-compact-card__value solduri-compact-card__value--sold">
|
||||||
|
{{ soldTotal !== undefined && soldTotal > 0 ? formatAmount(soldTotal) : '0 ✓' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
class="pi pi-chevron-down solduri-compact-card__chevron"
|
class="pi pi-chevron-down solduri-compact-card__chevron"
|
||||||
@@ -90,10 +99,17 @@
|
|||||||
<!-- TVA / Datorii Buget: Breakdown pe grupe (TVA/BASS/CAM) cu sub-conturi -->
|
<!-- TVA / Datorii Buget: Breakdown pe grupe (TVA/BASS/CAM) cu sub-conturi -->
|
||||||
<template v-else-if="type === 'tva'">
|
<template v-else-if="type === 'tva'">
|
||||||
<template v-if="Array.isArray(breakdown) && (breakdown as any[]).length">
|
<template v-if="Array.isArray(breakdown) && (breakdown as any[]).length">
|
||||||
|
<!-- Header coloane -->
|
||||||
|
<div class="solduri-compact-card__debt-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="solduri-compact-card__col-head">Datorat</span>
|
||||||
|
<span class="solduri-compact-card__col-head">Sold</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-for="group in (breakdown as any[])" :key="group.key">
|
<div v-for="group in (breakdown as any[])" :key="group.key">
|
||||||
<!-- Rând grup -->
|
<!-- Rând grup -->
|
||||||
<div
|
<div
|
||||||
class="solduri-compact-card__breakdown-item solduri-compact-card__breakdown-group"
|
class="solduri-compact-card__breakdown-group"
|
||||||
@click.stop="toggleGroup(group.key)"
|
@click.stop="toggleGroup(group.key)"
|
||||||
>
|
>
|
||||||
<span class="solduri-compact-card__breakdown-label solduri-compact-card__group-label">
|
<span class="solduri-compact-card__breakdown-label solduri-compact-card__group-label">
|
||||||
@@ -102,10 +118,14 @@
|
|||||||
{{ group.label }}
|
{{ group.label }}
|
||||||
</span>
|
</span>
|
||||||
<span class="solduri-compact-card__breakdown-value">
|
<span class="solduri-compact-card__breakdown-value">
|
||||||
{{ group.precedent !== 0 ? formatAmount(group.precedent) : '-' }}
|
{{ group.datorat > 0 ? formatAmount(group.datorat) : '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="solduri-compact-card__breakdown-value"
|
||||||
|
:class="group.sold <= 0 && group.datorat > 0 ? 'solduri-compact-card__cleared' : ''">
|
||||||
|
{{ group.sold > 0 ? formatAmount(group.sold) : (group.datorat > 0 ? '✓' : '-') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Sub-conturi -->
|
<!-- Sub-conturi expandabile -->
|
||||||
<div v-show="expandedGroups.has(group.key)">
|
<div v-show="expandedGroups.has(group.key)">
|
||||||
<div
|
<div
|
||||||
v-for="acc in group.sub_accounts"
|
v-for="acc in group.sub_accounts"
|
||||||
@@ -114,11 +134,32 @@
|
|||||||
>
|
>
|
||||||
<span class="solduri-compact-card__breakdown-sublabel">{{ acc.label }}</span>
|
<span class="solduri-compact-card__breakdown-sublabel">{{ acc.label }}</span>
|
||||||
<span class="solduri-compact-card__breakdown-subvalue">
|
<span class="solduri-compact-card__breakdown-subvalue">
|
||||||
{{ acc.precedent !== 0 ? formatAmount(acc.precedent) : '-' }}
|
{{ acc.datorat > 0 ? formatAmount(acc.datorat) : '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="solduri-compact-card__breakdown-subvalue">
|
||||||
|
{{ acc.sold > 0 ? formatAmount(acc.sold) : (acc.datorat > 0 ? '✓' : '-') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Obligații luna curentă -->
|
||||||
|
<template v-if="(breakdown as any[]).some((g: any) => g.curent > 0)">
|
||||||
|
<div class="solduri-compact-card__divider"></div>
|
||||||
|
<div class="solduri-compact-card__curent-summary">
|
||||||
|
<span class="solduri-compact-card__curent-title">Obligații curente:</span>
|
||||||
|
<span
|
||||||
|
v-for="g in (breakdown as any[]).filter((g: any) => g.curent > 0)"
|
||||||
|
:key="'c-' + g.key"
|
||||||
|
class="solduri-compact-card__curent-chip"
|
||||||
|
>
|
||||||
|
{{ g.label }} {{ formatAmount(g.curent) }}
|
||||||
|
</span>
|
||||||
|
<span class="solduri-compact-card__curent-total">
|
||||||
|
= {{ formatAmount(curentTotal) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="solduri-compact-card__breakdown-item">
|
<div class="solduri-compact-card__breakdown-item">
|
||||||
@@ -163,6 +204,7 @@ type BreakdownType = TrezorerieBreakdown | ClientiFurnizoriBreakdown | null
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: CardType
|
type: CardType
|
||||||
total: number
|
total: number
|
||||||
|
soldTotal?: number
|
||||||
breakdown?: BreakdownType
|
breakdown?: BreakdownType
|
||||||
casaTotal?: number
|
casaTotal?: number
|
||||||
bancaTotal?: number
|
bancaTotal?: number
|
||||||
@@ -203,6 +245,14 @@ const valueColorClass = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Computed: Total obligații curente (doar pentru type === 'tva')
|
||||||
|
const curentTotal = computed(() => {
|
||||||
|
if (!Array.isArray(props.breakdown)) return 0
|
||||||
|
return (props.breakdown as any[])
|
||||||
|
.filter((g: any) => Number(g.curent || 0) > 0)
|
||||||
|
.reduce((sum: number, g: any) => sum + Number(g.curent || 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
// Computed: Check if breakdown data exists
|
// Computed: Check if breakdown data exists
|
||||||
const hasBreakdown = computed(() => {
|
const hasBreakdown = computed(() => {
|
||||||
if (props.type === 'trezorerie') {
|
if (props.type === 'trezorerie') {
|
||||||
@@ -303,6 +353,24 @@ const formatPeriodLabel = (key: string): string => {
|
|||||||
line-height: var(--leading-tight);
|
line-height: var(--leading-tight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Debt line: datorat · sold inline, same font */
|
||||||
|
.solduri-compact-card__debt-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__debt-sep {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__value--sold {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Value color modifiers */
|
/* Value color modifiers */
|
||||||
.solduri-compact-card__value--success {
|
.solduri-compact-card__value--success {
|
||||||
color: var(--green-600);
|
color: var(--green-600);
|
||||||
@@ -385,10 +453,35 @@ const formatPeriodLabel = (key: string): string => {
|
|||||||
font-family: var(--font-mono, monospace);
|
font-family: var(--font-mono, monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TVA: 3-column grid layout (label | datorat | sold) */
|
||||||
|
.solduri-compact-card__debt-header,
|
||||||
|
.solduri-compact-card__breakdown-group,
|
||||||
|
.solduri-compact-card__breakdown-subitem {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__col-head {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
text-align: right;
|
||||||
|
min-width: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override: breakdown-value/subvalue right-aligned with min-width in grid */
|
||||||
|
.solduri-compact-card__breakdown-group .solduri-compact-card__breakdown-value,
|
||||||
|
.solduri-compact-card__breakdown-subitem .solduri-compact-card__breakdown-subvalue {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
/* TVA grup toggle */
|
/* TVA grup toggle */
|
||||||
.solduri-compact-card__breakdown-group {
|
.solduri-compact-card__breakdown-group {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: var(--font-semibold);
|
font-weight: var(--font-semibold);
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
}
|
}
|
||||||
.solduri-compact-card__breakdown-group:hover { background: var(--surface-hover); }
|
.solduri-compact-card__breakdown-group:hover { background: var(--surface-hover); }
|
||||||
|
|
||||||
@@ -405,6 +498,49 @@ const formatPeriodLabel = (key: string): string => {
|
|||||||
}
|
}
|
||||||
.solduri-compact-card__group-toggle--expanded { transform: rotate(90deg); }
|
.solduri-compact-card__group-toggle--expanded { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
/* Cleared (paid) indicator */
|
||||||
|
.solduri-compact-card__cleared {
|
||||||
|
color: var(--green-600);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divider between breakdown and obligații curente */
|
||||||
|
.solduri-compact-card__divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--surface-border);
|
||||||
|
margin: var(--space-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Obligații curente section */
|
||||||
|
.solduri-compact-card__curent-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__curent-title {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__curent-chip {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 2px var(--space-xs);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solduri-compact-card__curent-total {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive - Mobile */
|
/* Responsive - Mobile */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.solduri-compact-card {
|
.solduri-compact-card {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<div class="metrics-cards-section">
|
<div class="metrics-cards-section">
|
||||||
<!-- Mobile: Swipeable KPI Cards Carousel -->
|
<!-- 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 -->
|
<!-- 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">
|
<SwipeableCards v-if="isMobile" :totalCards="6" :fixed-dots="true" :fill-height="true" class="mobile-kpi-carousel">
|
||||||
<!-- Page 1: Grid 2x2 cu Solduri Compacte -->
|
<!-- Page 1: Grid 2x2 cu Solduri Compacte -->
|
||||||
<template #card-0>
|
<template #card-0>
|
||||||
<div class="solduri-grid-2x2">
|
<div class="solduri-grid-2x2">
|
||||||
@@ -73,7 +73,8 @@
|
|||||||
/>
|
/>
|
||||||
<SolduriCompactCard
|
<SolduriCompactCard
|
||||||
type="tva"
|
type="tva"
|
||||||
:total="tvaPreviousMonthNet"
|
:total="budgetDebtTotalPrecedent"
|
||||||
|
:sold-total="budgetDebtSold"
|
||||||
:breakdown="tvaBreakdown"
|
:breakdown="tvaBreakdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,14 +229,15 @@
|
|||||||
</CollapsibleCard>
|
</CollapsibleCard>
|
||||||
<CollapsibleCard
|
<CollapsibleCard
|
||||||
label="Datorii la buget"
|
label="Datorii la buget"
|
||||||
:value="budgetDebtTotalPrecedent"
|
:value="budgetDebtHeaderValue"
|
||||||
:value-class="budgetDebtTotalPrecedent > 0 ? 'negative' : 'positive'"
|
:value-class="budgetDebtSold > 0 ? 'negative' : (budgetDebtTotalPrecedent > 0 ? 'positive' : '')"
|
||||||
>
|
>
|
||||||
<div class="budget-debt-breakdown-desktop">
|
<div class="budget-debt-breakdown-desktop">
|
||||||
<div class="budget-debt-breakdown-header">
|
<div class="budget-debt-breakdown-header">
|
||||||
<span class="budget-debt-col-label">Categorie</span>
|
<span></span>
|
||||||
<span class="budget-debt-col-header-value">Luna prec.</span>
|
<span class="budget-debt-col-header-value">Datorat</span>
|
||||||
<span class="budget-debt-col-header-value">Luna curentă</span>
|
<span class="budget-debt-col-header-value budget-debt-col-achitat">Achitat</span>
|
||||||
|
<span class="budget-debt-col-header-value">Sold</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-for="group in budgetDebtBreakdown" :key="group.key">
|
<template v-for="group in budgetDebtBreakdown" :key="group.key">
|
||||||
@@ -250,10 +252,13 @@
|
|||||||
{{ group.label }}
|
{{ group.label }}
|
||||||
</span>
|
</span>
|
||||||
<span class="budget-debt-col-value">
|
<span class="budget-debt-col-value">
|
||||||
{{ group.precedent !== 0 ? formatAmount(group.precedent) : '-' }}
|
{{ group.datorat > 0 ? formatAmount(group.datorat) : '-' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="budget-debt-col-value">
|
<span class="budget-debt-col-value budget-debt-paid budget-debt-col-achitat">
|
||||||
{{ group.curent !== 0 ? formatAmount(group.curent) : '-' }}
|
{{ group.achitat > 0 ? formatAmount(group.achitat) : '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="budget-debt-col-value" :class="{ 'budget-debt-cleared': group.sold <= 0 && group.datorat > 0 }">
|
||||||
|
{{ group.sold > 0 ? formatAmount(group.sold) : (group.datorat > 0 ? '✓' : '-') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -266,10 +271,13 @@
|
|||||||
>
|
>
|
||||||
<span class="budget-debt-col-label budget-debt-sub-label">{{ acc.label }}</span>
|
<span class="budget-debt-col-label budget-debt-sub-label">{{ acc.label }}</span>
|
||||||
<span class="budget-debt-col-value budget-debt-sub-value">
|
<span class="budget-debt-col-value budget-debt-sub-value">
|
||||||
{{ acc.precedent !== 0 ? formatAmount(acc.precedent) : '-' }}
|
{{ acc.datorat > 0 ? formatAmount(acc.datorat) : '-' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="budget-debt-col-value budget-debt-sub-value">
|
<span class="budget-debt-col-value budget-debt-sub-value budget-debt-paid budget-debt-col-achitat">
|
||||||
{{ acc.curent !== 0 ? formatAmount(acc.curent) : '-' }}
|
{{ acc.achitat > 0 ? formatAmount(acc.achitat) : '-' }}
|
||||||
|
</span>
|
||||||
|
<span class="budget-debt-col-value budget-debt-sub-value" :class="{ 'budget-debt-cleared': acc.sold <= 0 && acc.datorat > 0 }">
|
||||||
|
{{ acc.sold > 0 ? formatAmount(acc.sold) : (acc.datorat > 0 ? '✓' : '-') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,6 +286,20 @@
|
|||||||
<div v-if="budgetDebtBreakdown.length === 0" class="budget-debt-breakdown-empty">
|
<div v-if="budgetDebtBreakdown.length === 0" class="budget-debt-breakdown-empty">
|
||||||
Nu există datorii înregistrate
|
Nu există datorii înregistrate
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Obligații luna curentă - compact, fără sub-secțiune -->
|
||||||
|
<template v-if="budgetDebtBreakdownCurent.length > 0">
|
||||||
|
<div class="budget-debt-section-divider"></div>
|
||||||
|
<div class="budget-debt-curent-summary">
|
||||||
|
<span class="budget-debt-curent-title">Obligații curente:</span>
|
||||||
|
<span
|
||||||
|
v-for="group in budgetDebtBreakdownCurent"
|
||||||
|
:key="'crt-' + group.key"
|
||||||
|
class="budget-debt-curent-chip"
|
||||||
|
>{{ group.label }} {{ formatAmount(group.curent) }}</span>
|
||||||
|
<span class="budget-debt-curent-total">= {{ formatAmount(budgetDebtTotalCurent) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleCard>
|
</CollapsibleCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -592,17 +614,44 @@ const tvaPreviousMonthNet = computed(() => {
|
|||||||
return (prev.plata || 0) - (prev.recuperat || 0);
|
return (prev.plata || 0) - (prev.recuperat || 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Breakdown pe grupe datorii buget (TVA/BASS/CAM cu sub-conturi)
|
// Breakdown pe grupe datorii buget - DOAR grupe cu datorii din luna precedentă
|
||||||
const budgetDebtBreakdown = computed(() => {
|
const budgetDebtBreakdown = computed(() => {
|
||||||
return dashboardStore.summary?.budget_debt_breakdown || [];
|
return (dashboardStore.summary?.budget_debt_breakdown || [])
|
||||||
|
.filter(g => Number(g.datorat || 0) !== 0 || Number(g.precedent || 0) !== 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Filtrare grupe cu obligații luna curentă (pentru secțiunea compactă)
|
||||||
|
const budgetDebtBreakdownCurent = computed(() =>
|
||||||
|
(dashboardStore.summary?.budget_debt_breakdown || []).filter(g => Number(g.curent || 0) > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Total obligații curente (suma tuturor grupurilor din luna curentă)
|
||||||
|
const budgetDebtTotalCurent = computed(() =>
|
||||||
|
budgetDebtBreakdownCurent.value.reduce((sum, g) => sum + Number(g.curent || 0), 0)
|
||||||
|
);
|
||||||
|
|
||||||
// Total precedent din toate grupurile (pentru header CollapsibleCard)
|
// Total precedent din toate grupurile (pentru header CollapsibleCard)
|
||||||
const budgetDebtTotalPrecedent = computed(() => {
|
const budgetDebtTotalPrecedent = computed(() => {
|
||||||
const groups = dashboardStore.summary?.budget_debt_breakdown || [];
|
const groups = dashboardStore.summary?.budget_debt_breakdown || [];
|
||||||
return groups.reduce((sum, g) => sum + Number(g.precedent || 0), 0);
|
return groups.reduce((sum, g) => sum + Number(g.precedent || 0), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sold final total (cât mai rămâne de plată după plățile din luna curentă)
|
||||||
|
const budgetDebtSold = computed(() => {
|
||||||
|
const groups = dashboardStore.summary?.budget_debt_breakdown || [];
|
||||||
|
return groups.reduce((sum, g) => sum + Number(g.sold || 0), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Header card: format "X.XXX (Y.YYY)" — total datorat și sold rămas
|
||||||
|
const budgetDebtHeaderValue = computed(() => {
|
||||||
|
const datorat = budgetDebtTotalPrecedent.value;
|
||||||
|
const sold = budgetDebtSold.value;
|
||||||
|
if (datorat === 0 && sold === 0) return '-';
|
||||||
|
const fmt = (n) => new Intl.NumberFormat('ro-RO').format(Math.round(Math.abs(n)));
|
||||||
|
if (sold <= 0 && datorat > 0) return `${fmt(datorat)} (0 ✓)`;
|
||||||
|
return `${fmt(datorat)} (${fmt(sold)})`;
|
||||||
|
});
|
||||||
|
|
||||||
const tvaBreakdown = computed(() => {
|
const tvaBreakdown = computed(() => {
|
||||||
return dashboardStore.summary?.budget_debt_breakdown || [];
|
return dashboardStore.summary?.budget_debt_breakdown || [];
|
||||||
});
|
});
|
||||||
@@ -1658,6 +1707,39 @@ onUnmounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile Budget Card wrapper (card-6 in SwipeableCards) */
|
||||||
|
.mobile-budget-card {
|
||||||
|
background: var(--surface-card);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-budget-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border-bottom: 1px solid var(--surface-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-budget-card-title {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-budget-card-value {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-budget-card-value.positive { color: var(--green-600); }
|
||||||
|
.mobile-budget-card-value.negative { color: var(--red-600); }
|
||||||
|
|
||||||
/* Budget Debt Breakdown in Desktop CollapsibleCard */
|
/* Budget Debt Breakdown in Desktop CollapsibleCard */
|
||||||
.budget-debt-breakdown-desktop {
|
.budget-debt-breakdown-desktop {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1667,7 +1749,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.budget-debt-breakdown-header {
|
.budget-debt-breakdown-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: 1fr auto auto auto;
|
||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
padding: var(--space-xs) 0 var(--space-sm) 0;
|
padding: var(--space-xs) 0 var(--space-sm) 0;
|
||||||
border-bottom: 1px solid var(--surface-border);
|
border-bottom: 1px solid var(--surface-border);
|
||||||
@@ -1676,7 +1758,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.budget-debt-breakdown-row {
|
.budget-debt-breakdown-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: 1fr auto auto auto;
|
||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
padding: var(--space-xs) 0;
|
padding: var(--space-xs) 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1701,7 +1783,7 @@ onUnmounted(() => {
|
|||||||
font-family: var(--font-mono, monospace);
|
font-family: var(--font-mono, monospace);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 130px;
|
min-width: 100px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1712,10 +1794,29 @@ onUnmounted(() => {
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 130px;
|
min-width: 100px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Coloana "Achitat" - verde pentru plăți efectuate */
|
||||||
|
.budget-debt-paid {
|
||||||
|
color: var(--green-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .budget-debt-paid {
|
||||||
|
color: var(--green-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coloana "Sold" când e 0 (datoria achitată integral) */
|
||||||
|
.budget-debt-cleared {
|
||||||
|
color: var(--green-600);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .budget-debt-cleared {
|
||||||
|
color: var(--green-400);
|
||||||
|
}
|
||||||
|
|
||||||
.budget-debt-breakdown-empty {
|
.budget-debt-breakdown-empty {
|
||||||
padding: var(--space-sm) 0;
|
padding: var(--space-sm) 0;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
@@ -1756,6 +1857,66 @@ onUnmounted(() => {
|
|||||||
.budget-debt-sub-label { padding-left: var(--space-md); font-size: var(--text-xs); }
|
.budget-debt-sub-label { padding-left: var(--space-md); font-size: var(--text-xs); }
|
||||||
.budget-debt-sub-value { font-size: var(--text-xs); }
|
.budget-debt-sub-value { font-size: var(--text-xs); }
|
||||||
|
|
||||||
|
/* Mobile: reduce columns (hide Achitat, shrink values) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.budget-debt-breakdown-header,
|
||||||
|
.budget-debt-breakdown-row {
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-col-achitat {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-col-value {
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-breakdown-desktop {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separator între precedent și curent */
|
||||||
|
.budget-debt-section-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--surface-border);
|
||||||
|
margin: var(--space-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Obligații curente - compact, o singură linie */
|
||||||
|
.budget-debt-curent-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-curent-title {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-curent-chip {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-debt-curent-total {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive - All breakpoints consolidated */
|
/* Responsive - All breakpoints consolidated */
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.metrics-row {
|
.metrics-row {
|
||||||
@@ -1787,6 +1948,74 @@ onUnmounted(() => {
|
|||||||
margin-bottom: var(--space-lg);
|
margin-bottom: var(--space-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Eliminare culori semantice în contextul caruselului mobil */
|
||||||
|
/* Clase globale utility (colors.css): text-success/error/primary/danger */
|
||||||
|
.mobile-kpi-carousel :deep(.text-success),
|
||||||
|
.mobile-kpi-carousel :deep(.text-error),
|
||||||
|
.mobile-kpi-carousel :deep(.text-primary),
|
||||||
|
.mobile-kpi-carousel :deep(.text-danger) {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clase positive/negative pe header-total (Clienti/FurnizoriBalanceCard) */
|
||||||
|
.mobile-kpi-carousel :deep(.header-total.positive),
|
||||||
|
.mobile-kpi-carousel :deep(.header-total.negative),
|
||||||
|
.mobile-kpi-carousel :deep(.positive),
|
||||||
|
.mobile-kpi-carousel :deep(.negative) {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clase value--success/danger (SolduriCompactCard) */
|
||||||
|
.mobile-kpi-carousel :deep(.solduri-compact-card__value--success),
|
||||||
|
.mobile-kpi-carousel :deep(.solduri-compact-card__value--danger) {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trend colors - neutralizate */
|
||||||
|
.mobile-kpi-carousel :deep(.trend-up),
|
||||||
|
.mobile-kpi-carousel :deep(.trend-down) {
|
||||||
|
color: var(--text-color-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Eliminare chenar exterior pe cardurile din carusel (paginile 2+)
|
||||||
|
pentru a avea același stil ca pagina 1 (solduri grid fără chenar) */
|
||||||
|
.mobile-kpi-carousel :deep(.metric-card) {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-kpi-carousel :deep(.financial-indicators-card) {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-kpi-carousel .mobile-budget-card {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uniformizare dimensiuni fonturi în cardurile din carusel */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
/* Valorile principale din toate tipurile de carduri */
|
||||||
|
.mobile-kpi-carousel :deep(.metric-value),
|
||||||
|
.mobile-kpi-carousel :deep(.treasury-value),
|
||||||
|
.mobile-kpi-carousel :deep(.header-total),
|
||||||
|
.mobile-kpi-carousel :deep(.solduri-compact-card__value) {
|
||||||
|
font-size: 1.5rem !important;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label-uri card */
|
||||||
|
.mobile-kpi-carousel :deep(.metric-label),
|
||||||
|
.mobile-kpi-carousel :deep(.treasury-label),
|
||||||
|
.mobile-kpi-carousel :deep(.header-label) {
|
||||||
|
font-size: var(--text-xs) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* US-2002: Solduri list for mobile first page - 1 card per row */
|
/* US-2002: Solduri list for mobile first page - 1 card per row */
|
||||||
.solduri-grid-2x2 {
|
.solduri-grid-2x2 {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,26 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="swipeable-cards-container">
|
<div
|
||||||
<!-- Cards Track -->
|
class="swipeable-cards-container"
|
||||||
<div
|
:class="{ 'swipeable-cards-container--fill': fillHeight }"
|
||||||
class="cards-track"
|
@touchstart="handleTouchStart"
|
||||||
ref="trackRef"
|
@touchmove="handleTouchMove"
|
||||||
:style="trackStyle"
|
@touchend="handleTouchEnd"
|
||||||
@touchstart="handleTouchStart"
|
>
|
||||||
@touchmove="handleTouchMove"
|
<!-- Cards Track Wrapper - overflow:hidden only here so dots aren't clipped -->
|
||||||
@touchend="handleTouchEnd"
|
<!-- Height tracks active card's natural content height via JS to prevent flex-stretch distortion -->
|
||||||
>
|
<div class="cards-track-overflow" :style="{ height: activeCardHeight }">
|
||||||
<div
|
<div
|
||||||
v-for="(_, index) in totalCards"
|
class="cards-track"
|
||||||
:key="index"
|
ref="trackRef"
|
||||||
class="card-slide"
|
:style="trackStyle"
|
||||||
:class="{ active: index === currentIndex }"
|
|
||||||
>
|
>
|
||||||
<slot :name="`card-${index}`"></slot>
|
<div
|
||||||
|
v-for="(_, index) in totalCards"
|
||||||
|
:key="index"
|
||||||
|
class="card-slide"
|
||||||
|
:class="{ active: index === currentIndex }"
|
||||||
|
>
|
||||||
|
<slot :name="`card-${index}`"></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dots Indicator -->
|
<!-- Dots Indicator - fixed above MobileBottomNav when fixedDots=true, otherwise in normal flow -->
|
||||||
<div class="dots-indicator" v-if="showDots && totalCards > 1">
|
<div
|
||||||
|
class="dots-indicator"
|
||||||
|
:class="{ 'dots-indicator--fixed': fixedDots }"
|
||||||
|
v-if="showDots && totalCards > 1"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
v-for="index in totalCards"
|
v-for="index in totalCards"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -34,7 +44,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SwipeableCards - Touch swipeable carousel for mobile KPI cards
|
* SwipeableCards - Touch swipeable carousel for mobile KPI cards
|
||||||
@@ -75,6 +85,22 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Fix dots above MobileBottomNav (position: fixed, bottom: 56px).
|
||||||
|
* Use on mobile pages with a fixed bottom nav bar.
|
||||||
|
*/
|
||||||
|
fixedDots: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Fill available viewport height so the entire screen is swipeable.
|
||||||
|
* Use on full-page carousels (e.g., mobile dashboard).
|
||||||
|
*/
|
||||||
|
fillHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Whether to auto-advance cards
|
* Whether to auto-advance cards
|
||||||
*/
|
*/
|
||||||
@@ -99,6 +125,39 @@ const currentIndex = ref(0)
|
|||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const dragOffset = ref(0)
|
const dragOffset = ref(0)
|
||||||
|
|
||||||
|
// Dynamic height tracking - makes overflow wrapper match active card's natural height
|
||||||
|
const activeCardHeight = ref('auto')
|
||||||
|
let resizeObserver = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update overflow wrapper height to match the active card's natural content height.
|
||||||
|
* Uses firstElementChild.scrollHeight to bypass flex-stretch distortion.
|
||||||
|
*/
|
||||||
|
const updateActiveHeight = async () => {
|
||||||
|
await nextTick()
|
||||||
|
if (!trackRef.value) return
|
||||||
|
const slides = Array.from(trackRef.value.children)
|
||||||
|
const activeSlide = slides[currentIndex.value]
|
||||||
|
if (activeSlide) {
|
||||||
|
const h = activeSlide.firstElementChild?.scrollHeight || activeSlide.scrollHeight
|
||||||
|
if (h > 0) activeCardHeight.value = `${h}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch each card's content for height changes (e.g. loading → data transitions).
|
||||||
|
* ResizeObserver fires when the card's content height changes.
|
||||||
|
*/
|
||||||
|
const setupResizeObserver = () => {
|
||||||
|
if (typeof ResizeObserver === 'undefined' || !trackRef.value) return
|
||||||
|
resizeObserver = new ResizeObserver(updateActiveHeight)
|
||||||
|
Array.from(trackRef.value.children).forEach(slide => {
|
||||||
|
if (slide.firstElementChild) {
|
||||||
|
resizeObserver.observe(slide.firstElementChild)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Touch tracking state
|
// Touch tracking state
|
||||||
let touchStartX = 0
|
let touchStartX = 0
|
||||||
let touchStartY = 0
|
let touchStartY = 0
|
||||||
@@ -136,6 +195,7 @@ const goToCard = (index) => {
|
|||||||
if (index < 0 || index >= props.totalCards) return
|
if (index < 0 || index >= props.totalCards) return
|
||||||
currentIndex.value = index
|
currentIndex.value = index
|
||||||
emit('update:currentIndex', index)
|
emit('update:currentIndex', index)
|
||||||
|
updateActiveHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -306,6 +366,8 @@ watch(() => props.totalCards, (newTotal) => {
|
|||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
updateActiveHeight()
|
||||||
|
setupResizeObserver()
|
||||||
if (props.autoPlay) {
|
if (props.autoPlay) {
|
||||||
startAutoPlay()
|
startAutoPlay()
|
||||||
}
|
}
|
||||||
@@ -313,6 +375,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopAutoPlay()
|
stopAutoPlay()
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
resizeObserver = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Expose methods for parent component
|
// Expose methods for parent component
|
||||||
@@ -333,9 +399,25 @@ defineExpose({
|
|||||||
.swipeable-cards-container {
|
.swipeable-cards-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
/* Touch action on container since swipe handlers are here */
|
||||||
|
touch-action: pan-y pinch-zoom;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill available viewport height — full-screen swipe area.
|
||||||
|
56px top bar + 56px bottom nav = 112px reserved. */
|
||||||
|
.swipeable-cards-container--fill {
|
||||||
|
min-height: calc(100vh - 112px);
|
||||||
|
min-height: calc(100dvh - 112px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overflow wrapper - clips the sliding track horizontally, NOT the dots */
|
||||||
|
/* Height is set dynamically via JS to match the active card's natural content height */
|
||||||
|
.cards-track-overflow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* Padding for dots indicator */
|
width: 100%;
|
||||||
padding-bottom: var(--space-xl);
|
transition: height var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards track - horizontal scrolling container */
|
/* Cards track - horizontal scrolling container */
|
||||||
@@ -343,11 +425,6 @@ defineExpose({
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
/* Prevent text selection during swipe */
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
/* Smooth touch scrolling */
|
|
||||||
touch-action: pan-y pinch-zoom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Individual card slide */
|
/* Individual card slide */
|
||||||
@@ -373,15 +450,21 @@ defineExpose({
|
|||||||
================================================ */
|
================================================ */
|
||||||
|
|
||||||
.dots-indicator {
|
.dots-indicator {
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
padding: var(--space-sm) 0;
|
padding: var(--space-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fixed variant - floats above MobileBottomNav (56px height) */
|
||||||
|
.dots-indicator--fixed {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 56px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 99;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Individual dot */
|
/* Individual dot */
|
||||||
|
|||||||
Reference in New Issue
Block a user