feat(reports,mobile): add FinancialIndicators page and refactor dashboard layout

- Add dedicated FinancialIndicatorsView with its own route
- Refactor FinancialIndicatorsCard: simplified collapsible card (no period picker)
- Dashboard mobile: reduce swipeable pages from 2 to 1, embed indicators in card-0
- Dashboard desktop: wrap indicators in CollapsibleCard
- Update MobileBottomNav defaults: reports-centric nav (Facturi/Banca/Casa/Indicatori)
- BankView/CashView: date format DD/MM/YY, remove amount color classes, CSS tweaks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-01 08:55:11 +00:00
parent 06cbf8fb9d
commit 74dff2d17d
14 changed files with 1624 additions and 2811 deletions

View File

@@ -156,7 +156,7 @@
@media (max-width: 768px) {
.app-container,
.page-container {
padding: var(--space-md);
padding: var(--space-xs);
}
/* US-705: Keep main-content padding minimal - app-container handles content spacing */
@@ -171,7 +171,7 @@
.dashboard-container {
gap: var(--space-lg);
padding: var(--space-md);
padding: var(--space-xs);
}
.card-container {
@@ -195,7 +195,7 @@
.app-container,
.page-container,
.dashboard-container {
padding: var(--space-sm);
padding: var(--space-xs);
}
/* US-705: Keep main-content padding minimal on small screens too */

View File

@@ -956,3 +956,22 @@
color: var(--text-color-secondary, #9ca3af) !important;
}
}
/* ===== Mobile Layout: Flat Cards (no borders/shadows) ===== */
/* Applied when parent has .mobile-layout class (isMobile === true).
Removes PrimeVue Card visual frame so content flows as flat list,
consistent with Material Design 3 edge-to-edge pattern. */
.mobile-layout .p-card {
border: none !important;
box-shadow: none !important;
border-radius: 0 !important;
background: transparent !important;
}
.mobile-layout .p-card .p-card-body {
padding: var(--space-sm) !important;
}
.mobile-layout .p-card .p-card-content {
padding: 0 !important;
}

View File

@@ -1,92 +1,77 @@
<template>
<div class="indicator-item" :class="statusClass">
<!-- Label (top) cu toggle pentru descriere -->
<div class="indicator-label">
{{ label }}
<i
v-if="description"
class="pi desc-toggle"
:class="descExpanded ? 'pi-chevron-up' : 'pi-chevron-down'"
@click.stop="toggleDescription"
title="Toggle descriere"
></i>
</div>
<!-- Description (collapsible) -->
<div v-if="description && descExpanded" class="indicator-description slide-down">
{{ description }}
</div>
<!-- Main content: Value centered + Status icon on right -->
<div class="indicator-main">
<div class="indicator-value" :class="statusClass">
{{ formattedValue }}{{ unit ? ` ${unit}` : '' }}
</div>
<div class="indicator-status-icon" :class="statusClass">
<div class="indicator-item-flat" @click="expanded = !expanded">
<!-- Collapsed flat row: Label + Value + Status icon -->
<div class="indicator-row-flat">
<span class="indicator-label">{{ label }}</span>
<span class="indicator-value">{{ formattedValue }}{{ unit ? ` ${unit}` : '' }}</span>
<span class="indicator-status-icon" :class="statusClass">
<i :class="statusIcon"></i>
</div>
</span>
</div>
<!-- Sparkline (bottom) -->
<div
v-if="hasSparklineData"
class="sparkline-container"
ref="sparklineContainer"
>
<svg
class="sparkline-svg"
:viewBox="`0 0 ${svgWidth} ${svgHeight}`"
preserveAspectRatio="none"
@mousemove="handleMouseMove"
@mouseleave="handleMouseLeave"
>
<!-- Sparkline polyline -->
<polyline
:points="sparklinePoints"
fill="none"
:stroke="strokeColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="sparkline-line"
/>
<!-- Expanded content (tap to reveal) -->
<div v-if="expanded" class="indicator-expand-flat slide-down">
<!-- Description -->
<div v-if="description" class="indicator-description">
{{ description }}
</div>
<!-- Hover point indicator -->
<circle
v-if="hoveredIndex !== null"
:cx="hoveredPoint.x"
:cy="hoveredPoint.y"
r="4"
:fill="strokeColor"
class="sparkline-point"
/>
</svg>
<!-- Tooltip -->
<!-- Sparkline -->
<div
v-if="hoveredIndex !== null"
class="sparkline-tooltip"
:style="tooltipStyle"
v-if="hasSparklineData"
class="sparkline-container"
ref="sparklineContainer"
>
<div class="tooltip-label">{{ tooltipLabel }}</div>
<div class="tooltip-value">{{ tooltipValue }}</div>
<svg
class="sparkline-svg"
:viewBox="`0 0 ${svgWidth} ${svgHeight}`"
preserveAspectRatio="none"
@mousemove="handleMouseMove"
@mouseleave="handleMouseLeave"
>
<polyline
:points="sparklinePoints"
fill="none"
:stroke="strokeColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="sparkline-line"
/>
<circle
v-if="hoveredIndex !== null"
:cx="hoveredPoint.x"
:cy="hoveredPoint.y"
r="4"
:fill="strokeColor"
class="sparkline-point"
/>
</svg>
<div
v-if="hoveredIndex !== null"
class="sparkline-tooltip"
:style="tooltipStyle"
>
<div class="tooltip-label">{{ tooltipLabel }}</div>
<div class="tooltip-value">{{ tooltipValue }}</div>
</div>
</div>
</div>
<!-- YoY Trend indicator (shows variation from first to last sparkline value) -->
<div
v-if="hasSparklineData && trendInfo.text !== '-'"
class="yoy-trend"
:class="trendInfo.class"
>
<i :class="trendInfo.icon"></i>
<span class="trend-value">{{ trendInfo.text }}</span>
<span class="trend-label">vs 12 luni</span>
</div>
<!-- YoY Trend -->
<div
v-if="hasSparklineData && trendInfo.text !== '-'"
class="yoy-trend"
:class="trendInfo.class"
>
<i :class="trendInfo.icon"></i>
<span class="trend-value">{{ trendInfo.text }}</span>
<span class="trend-label">vs 12 luni</span>
</div>
<!-- Threshold info -->
<div v-if="thresholdText" class="indicator-threshold">
{{ thresholdText }}
<!-- Threshold info -->
<div v-if="thresholdText" class="indicator-threshold">
{{ thresholdText }}
</div>
</div>
</div>
</template>
@@ -94,12 +79,8 @@
<script setup>
import { ref, computed } from 'vue'
// Description toggle state
const descExpanded = ref(false)
const toggleDescription = () => {
descExpanded.value = !descExpanded.value
}
// Expand/collapse state (tap to expand)
const expanded = ref(false)
const props = defineProps({
label: {
@@ -151,10 +132,13 @@ const sparklineContainer = ref(null)
const hoveredIndex = ref(null)
const mouseX = ref(0)
// Computed: Format the displayed value
// Computed: Format the displayed value (locale-aware with thousands separators)
const formattedValue = computed(() => {
if (props.value === null || props.value === undefined) return '-'
return Number(props.value).toFixed(props.decimals)
return Number(props.value).toLocaleString('ro-RO', {
minimumFractionDigits: 0,
maximumFractionDigits: props.decimals
})
})
// Computed: Check if we have sparkline data
@@ -258,7 +242,7 @@ const tooltipValue = computed(() => {
if (hoveredIndex.value === null) return ''
const value = pointPositions.value[hoveredIndex.value]?.value
if (value === null || value === undefined) return '-'
return `${Number(value).toFixed(props.decimals)}${props.unit ? ` ${props.unit}` : ''}`
return `${Number(value).toLocaleString('ro-RO', { minimumFractionDigits: 0, maximumFractionDigits: props.decimals })}${props.unit ? ` ${props.unit}` : ''}`
})
// Computed: Tooltip position style
@@ -357,60 +341,51 @@ const handleMouseLeave = () => {
</script>
<style scoped>
/* Indicator Item Container */
.indicator-item {
background: transparent;
border: 1px solid var(--surface-border);
border-radius: var(--radius-sm);
padding: var(--space-md);
/* Indicator Item - Flat row design */
.indicator-item-flat {
border-bottom: 1px solid var(--surface-border);
display: flex;
flex-direction: column;
gap: var(--space-xs);
position: relative;
transition: border-color var(--transition-fast);
}
.indicator-item:hover {
border-color: var(--surface-hover);
}
/* Label (top) with toggle */
.indicator-label {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-color-secondary);
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
}
/* Description toggle icon */
.desc-toggle {
font-size: var(--text-xs);
color: var(--text-color-secondary);
cursor: pointer;
padding: 2px;
border-radius: var(--radius-sm);
transition: all var(--transition-fast);
opacity: 0.6;
transition: background var(--transition-fast);
}
.desc-toggle:hover {
color: var(--primary-color);
opacity: 1;
.indicator-item-flat:last-child {
border-bottom: none;
}
.indicator-item-flat:active {
background: var(--surface-hover);
}
/* Description (collapsible) */
/* Collapsed flat row */
.indicator-row-flat {
display: flex;
align-items: center;
padding: var(--space-sm) 0;
min-height: 48px;
gap: var(--space-sm);
}
/* Label (left, flex-grow) */
.indicator-label {
flex: 1;
font-size: var(--text-base);
font-weight: var(--font-medium);
color: var(--text-color);
}
/* Expanded content area */
.indicator-expand-flat {
padding: 0 0 var(--space-sm);
}
/* Description */
.indicator-description {
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
text-align: center;
opacity: 0.8;
line-height: 1.3;
padding: var(--space-xs);
line-height: 1.4;
padding: var(--space-xs) var(--space-sm);
background: var(--surface-hover);
border-radius: var(--radius-sm);
margin-bottom: var(--space-xs);
@@ -432,30 +407,19 @@ const handleMouseLeave = () => {
}
}
/* Main section: Value + Status Icon */
.indicator-main {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
position: relative;
}
/* Value (large, centered) */
/* Value (inline, right of label) */
.indicator-value {
font-size: var(--text-2xl);
font-weight: var(--font-bold);
font-family: var(--font-mono);
text-align: center;
font-size: var(--text-base);
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
color: var(--text-color);
white-space: nowrap;
}
/* Status Icon (right side) */
/* Status Icon (rightmost) - only element that gets status color */
.indicator-status-icon {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
font-size: var(--text-lg);
flex-shrink: 0;
}
/* Status Colors - use 600 variants in light mode, 400 variants in dark mode for better contrast */
@@ -620,7 +584,7 @@ const handleMouseLeave = () => {
/* Threshold info */
.indicator-threshold {
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
text-align: center;
margin-top: var(--space-xs);
@@ -628,18 +592,6 @@ const handleMouseLeave = () => {
/* Responsive */
@media (max-width: 768px) {
.indicator-item {
padding: var(--space-sm);
}
.indicator-value {
font-size: var(--text-xl);
}
.indicator-status-icon {
font-size: var(--text-base);
}
.sparkline-container {
height: 32px;
}

View File

@@ -249,13 +249,11 @@
<span class="compact-partner">{{ reg.nume || 'Fără partener' }}</span>
<span
class="compact-amount"
:class="reg.incasari > 0 ? 'positive' : (reg.plati > 0 ? 'negative' : '')"
>
<template v-if="reg.incasari > 0">+{{ formatNumber(reg.incasari) }}</template>
<template v-else-if="reg.plati > 0">-{{ formatNumber(reg.plati) }}</template>
<template v-else>0</template>
</span>
<i :class="expandedMeta.has(index) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="meta-chevron"></i>
</div>
<div v-if="expandedMeta.has(index)" class="compact-meta">
<span v-if="reg.nume_cont_bancar" class="meta-item">{{ reg.nume_cont_bancar }}</span>
@@ -552,11 +550,12 @@ const formatDate = (dateString) => {
return format(new Date(dateString), "dd.MM.yyyy");
};
// Short date format for mobile cards (DD/MM)
// Short date format for mobile cards (DD/MM/YY)
const formatDateShort = (dateString) => {
if (!dateString) return "";
const date = new Date(dateString);
return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}`;
const year = String(date.getFullYear()).slice(-2);
return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}/${year}`;
};
// Compact number format
@@ -971,8 +970,8 @@ watch(
.mobile-layout .register-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);
padding-left: 0;
padding-right: 0;
}
/* Card Spacing */
@@ -1005,10 +1004,9 @@ watch(
.mobile-totals-bar {
background: var(--surface-card);
border: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
border-bottom: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-xs);
margin-bottom: var(--space-sm);
}
.mobile-totals-grid {
@@ -1054,7 +1052,7 @@ watch(
display: flex;
align-items: center;
min-height: 44px;
padding: var(--space-sm) var(--space-md);
padding: var(--space-sm) 0;
gap: var(--space-sm);
cursor: pointer;
}
@@ -1064,9 +1062,9 @@ watch(
}
.compact-date {
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
min-width: 40px;
min-width: 54px;
flex-shrink: 0;
}
@@ -1083,20 +1081,7 @@ watch(
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
flex-shrink: 0;
}
.compact-amount.positive {
color: var(--green-600);
}
.compact-amount.negative {
color: var(--red-600);
}
.meta-chevron {
font-size: var(--text-xs);
color: var(--text-color-secondary);
color: var(--text-color);
flex-shrink: 0;
}
@@ -1104,9 +1089,9 @@ watch(
display: flex;
flex-direction: column;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md) var(--space-sm) calc(40px + var(--space-md) + var(--space-sm));
padding: var(--space-xs) var(--space-xs) var(--space-sm) calc(54px + var(--space-sm));
background: var(--surface-hover);
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
}
@@ -1156,24 +1141,8 @@ watch(
Dark Mode Support
================================================ */
[data-theme="dark"] .compact-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .compact-amount.negative {
color: var(--red-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .compact-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .compact-amount.negative {
color: var(--red-400);
}
:root:not([data-theme]) .numeric-value.negative {
color: var(--red-400);
}
@@ -1189,7 +1158,7 @@ watch(
@media (max-width: 768px) {
.register-view {
padding: var(--space-md);
padding: var(--space-xs);
}
}
</style>

View File

@@ -249,13 +249,11 @@
<span class="compact-partner">{{ reg.nume || 'Fără partener' }}</span>
<span
class="compact-amount"
:class="reg.incasari > 0 ? 'positive' : (reg.plati > 0 ? 'negative' : '')"
>
<template v-if="reg.incasari > 0">+{{ formatNumber(reg.incasari) }}</template>
<template v-else-if="reg.plati > 0">-{{ formatNumber(reg.plati) }}</template>
<template v-else>0</template>
</span>
<i :class="expandedMeta.has(index) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="meta-chevron"></i>
</div>
<div v-if="expandedMeta.has(index)" class="compact-meta">
<span v-if="reg.nume_cont_bancar" class="meta-item">{{ reg.nume_cont_bancar }}</span>
@@ -552,11 +550,12 @@ const formatDate = (dateString) => {
return format(new Date(dateString), "dd.MM.yyyy");
};
// Short date format for mobile cards (DD/MM)
// Short date format for mobile cards (DD/MM/YY)
const formatDateShort = (dateString) => {
if (!dateString) return "";
const date = new Date(dateString);
return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}`;
const year = String(date.getFullYear()).slice(-2);
return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}/${year}`;
};
// Compact number format
@@ -971,8 +970,8 @@ watch(
.mobile-layout .register-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);
padding-left: 0;
padding-right: 0;
}
/* Card Spacing */
@@ -1005,10 +1004,9 @@ watch(
.mobile-totals-bar {
background: var(--surface-card);
border: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
border-bottom: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-xs);
margin-bottom: var(--space-sm);
}
.mobile-totals-grid {
@@ -1054,7 +1052,7 @@ watch(
display: flex;
align-items: center;
min-height: 44px;
padding: var(--space-sm) var(--space-md);
padding: var(--space-sm) 0;
gap: var(--space-sm);
cursor: pointer;
}
@@ -1064,9 +1062,9 @@ watch(
}
.compact-date {
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
min-width: 40px;
min-width: 54px;
flex-shrink: 0;
}
@@ -1083,20 +1081,7 @@ watch(
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
flex-shrink: 0;
}
.compact-amount.positive {
color: var(--green-600);
}
.compact-amount.negative {
color: var(--red-600);
}
.meta-chevron {
font-size: var(--text-xs);
color: var(--text-color-secondary);
color: var(--text-color);
flex-shrink: 0;
}
@@ -1104,9 +1089,9 @@ watch(
display: flex;
flex-direction: column;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md) var(--space-sm) calc(40px + var(--space-md) + var(--space-sm));
padding: var(--space-xs) var(--space-xs) var(--space-sm) calc(54px + var(--space-sm));
background: var(--surface-hover);
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
}
@@ -1156,24 +1141,8 @@ watch(
Dark Mode Support
================================================ */
[data-theme="dark"] .compact-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .compact-amount.negative {
color: var(--red-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .compact-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .compact-amount.negative {
color: var(--red-400);
}
:root:not([data-theme]) .numeric-value.negative {
color: var(--red-400);
}
@@ -1189,7 +1158,7 @@ watch(
@media (max-width: 768px) {
.register-view {
padding: var(--space-md);
padding: var(--space-xs);
}
}
</style>

View File

@@ -50,7 +50,7 @@
<div class="metrics-cards-section">
<!-- Mobile: Swipeable KPI Cards Carousel -->
<!-- 2 pages: enriched compact cards with embedded charts + financial indicators -->
<SwipeableCards v-if="isMobile" :totalCards="2" :fixed-dots="true" :fill-height="true" class="mobile-kpi-carousel">
<SwipeableCards v-if="isMobile" :totalCards="1" :fixed-dots="false" :fill-height="true" class="mobile-kpi-carousel">
<!-- Page 1: Compact cards with embedded sparkline charts -->
<template #card-0>
<div class="solduri-grid-2x2">
@@ -126,20 +126,15 @@
:sold-total="budgetDebtSold"
:breakdown="tvaBreakdown"
/>
<FinancialIndicatorsCard
:loading="dashboardStore.financialIndicators.loading"
:error="dashboardStore.financialIndicators.error"
:data="dashboardStore.financialIndicators.data"
:period="previousPeriodForIndicators"
mobile
/>
</div>
</template>
<!-- Page 2: FinancialIndicatorsCard (US-015) -->
<template #card-1>
<FinancialIndicatorsCard
:loading="dashboardStore.financialIndicators.loading"
:error="dashboardStore.financialIndicators.error"
:data="dashboardStore.financialIndicators.data"
:initial-period="previousPeriodForIndicators"
:cache-info="dashboardStore.financialIndicators.cacheInfo"
mobile
@period-change="handleFinancialIndicatorsPeriodChange"
/>
</template>
</SwipeableCards>
<!-- Desktop: Grid layout (carduri grafice originale) - collapsible by default -->
@@ -291,22 +286,22 @@
</template>
</div>
</CollapsibleCard>
<CollapsibleCard
label="Indicatori"
:value="indicatorsSummaryLabel"
value-class=""
>
<FinancialIndicatorsCard
:loading="dashboardStore.financialIndicators.loading"
:error="dashboardStore.financialIndicators.error"
:data="dashboardStore.financialIndicators.data"
:period="previousPeriodForIndicators"
/>
</CollapsibleCard>
</div>
</div>
<!-- Financial Indicators Section - Desktop Only (US-014) -->
<div v-if="!isMobile" class="financial-indicators-section">
<FinancialIndicatorsCard
:loading="dashboardStore.financialIndicators.loading"
:error="dashboardStore.financialIndicators.error"
:data="dashboardStore.financialIndicators.data"
:initial-period="previousPeriodForIndicators"
:cache-info="dashboardStore.financialIndicators.cacheInfo"
@period-change="handleFinancialIndicatorsPeriodChange"
/>
</div>
</div>
</main>
@@ -826,16 +821,6 @@ const handleRefresh = async () => {
await loadDashboardData();
};
// US-014: Handle period change from FinancialIndicatorsCard dropdown
const handleFinancialIndicatorsPeriodChange = async (period) => {
if (!companyStore.selectedCompany || !period) return;
await dashboardStore.loadFinancialIndicators(
companyStore.selectedCompany.id_firma,
period.luna,
period.an,
);
};
// Computed property pentru luna curentă - folosește perioada din period selector
const currentMonthLabel = computed(() => {
// Prioritate: period selector > dashboard current period > loading
@@ -856,6 +841,15 @@ const currentMonthLabel = computed(() => {
// Computed property pentru luna anterioară - pentru indicatorii financiari
// Luna curentă e în lucru, deci folosim luna anterioară pentru date finale
// Summary label for Indicators CollapsibleCard header (desktop)
const indicatorsSummaryLabel = computed(() => {
const d = dashboardStore.financialIndicators.data;
if (!d) return '';
const marja = d?.profitabilitate?.marja_profit_brut?.value;
if (marja === null || marja === undefined) return '';
return `${Number(marja).toLocaleString('ro-RO', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}% Marjă`;
});
const previousPeriodForIndicators = computed(() => {
if (!periodStore.selectedPeriod) return null;
@@ -1705,10 +1699,7 @@ onUnmounted(() => {
}
/* US-014: Financial Indicators Section - Desktop Only */
.financial-indicators-section {
margin-top: var(--space-lg);
width: 100%;
}
/* Mobile Budget Card wrapper (card-6 in SwipeableCards) */
.mobile-budget-card {
@@ -1988,11 +1979,7 @@ onUnmounted(() => {
box-shadow: none;
}
.mobile-kpi-carousel :deep(.financial-indicators-card) {
border: none;
background: transparent;
box-shadow: none;
}
/* FinancialIndicatorsCard now has its own card styling - do NOT strip it */
.mobile-kpi-carousel .mobile-budget-card {
border: none;
@@ -2031,4 +2018,5 @@ onUnmounted(() => {
.solduri-grid-2x2 > * {
/* Height auto - only as tall as content needs */
}
</style>

View File

@@ -352,7 +352,6 @@
<span class="partner-sold" :class="{ overdue: group.hasRestant }">
{{ formatCurrency(group.totalSold) }}
</span>
<i :class="isGroupExpanded(group.name) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" class="partner-chevron"></i>
</div>
</div>
<div v-if="isGroupExpanded(group.name)" class="partner-invoices">
@@ -590,7 +589,7 @@ const formatDate = (value) => {
return date.toLocaleDateString('ro-RO', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
year: '2-digit'
})
}
@@ -1455,21 +1454,19 @@ onUnmounted(() => {
.mobile-partner-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
gap: 0;
}
.mobile-partner-row {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
overflow: hidden;
background: transparent;
border-bottom: 1px solid var(--surface-border);
}
.partner-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-sm) var(--space-md);
padding: var(--space-sm) var(--space-xs);
cursor: pointer;
gap: var(--space-sm);
min-height: 44px;
@@ -1516,25 +1513,15 @@ onUnmounted(() => {
color: var(--text-color);
}
.partner-sold.overdue {
color: var(--red-600);
}
.partner-chevron {
font-size: 12px;
color: var(--text-color-secondary);
}
.partner-invoices {
border-top: 1px solid var(--surface-border);
background: var(--surface-hover);
}
.invoice-line {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-xs) var(--space-md);
padding: var(--space-xs) var(--space-xs);
border-bottom: 1px solid var(--surface-border);
gap: var(--space-sm);
}
@@ -1544,24 +1531,20 @@ onUnmounted(() => {
}
.invoice-ref {
font-size: var(--text-xs);
font-size: var(--text-sm);
color: var(--text-color-secondary);
flex: 1;
min-width: 0;
}
.invoice-sold {
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
font-size: var(--text-sm);
color: var(--text-color);
flex-shrink: 0;
}
.invoice-sold.overdue {
color: var(--red-600);
}
/* Empty data state */
.empty-data {
display: flex;
@@ -1657,11 +1640,6 @@ onUnmounted(() => {
background: var(--primary-800);
}
[data-theme="dark"] .partner-sold.overdue,
[data-theme="dark"] .invoice-sold.overdue {
color: var(--red-400);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .filter-chip {
background: var(--surface-100);
@@ -1670,10 +1648,5 @@ onUnmounted(() => {
:root:not([data-theme]) .filter-chip.active {
background: var(--primary-800);
}
:root:not([data-theme]) .partner-sold.overdue,
:root:not([data-theme]) .invoice-sold.overdue {
color: var(--red-400);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -632,17 +632,17 @@ const formatCompact = (amount) => {
const formatDate = (dateString) => {
if (!dateString) return "";
try {
return format(new Date(dateString), "dd/MM/yyyy", { locale: ro });
return format(new Date(dateString), "dd/MM/yy", { locale: ro });
} catch (error) {
return dateString;
}
};
// Short date format for mobile (DD/MM only)
// Short date format for mobile (DD/MM/YY with year)
const formatDateShort = (dateString) => {
if (!dateString) return "";
try {
return format(new Date(dateString), "dd/MM", { locale: ro });
return format(new Date(dateString), "dd/MM/yy", { locale: ro });
} catch (error) {
return "";
}
@@ -1025,8 +1025,8 @@ watch(
.mobile-layout .invoices {
padding-top: calc(56px + 48px + var(--space-md)); /* Account for MobileTopBar + tabs */
padding-bottom: calc(56px + var(--space-md)); /* Account for fixed MobileBottomNav */
padding-left: var(--space-md);
padding-right: var(--space-md);
padding-left: 0;
padding-right: 0;
}
/* ================================================
@@ -1154,9 +1154,8 @@ watch(
.mobile-totals-bar {
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
padding: var(--space-sm) var(--space-xs);
margin-bottom: var(--space-sm);
}
.mobile-totals-content {
@@ -1193,7 +1192,7 @@ watch(
align-items: center;
border-bottom: 1px solid var(--surface-border);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
padding: var(--space-sm) 0;
gap: var(--space-sm);
}
@@ -1346,7 +1345,7 @@ watch(
@media (max-width: 768px) {
.invoices {
padding: var(--space-md);
padding: var(--space-xs);
}
.page-title {

View File

@@ -1057,8 +1057,8 @@ watch(
.mobile-layout .trial-balance {
padding-top: calc(56px + var(--space-md)); /* Account for fixed MobileTopBar */
padding-bottom: calc(56px + var(--space-md)); /* Account for fixed MobileBottomNav */
padding-left: var(--space-md);
padding-right: var(--space-md);
padding-left: 0;
padding-right: 0;
}
/* Card Spacing */
@@ -1088,9 +1088,8 @@ watch(
.mobile-totals-bar {
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
padding: var(--space-sm) var(--space-xs);
margin-bottom: var(--space-sm);
}
.mobile-totals-content {
@@ -1142,7 +1141,7 @@ watch(
align-items: center;
border-bottom: 1px solid var(--surface-border);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
padding: var(--space-sm) 0;
gap: var(--space-sm);
}

View File

@@ -73,6 +73,12 @@ const routes = [
component: () => import('@reports/views/MaturityAnalysisView.vue'),
meta: { requiresAuth: true, title: 'Analiză Scadențe - ROA2WEB' }
},
{
path: 'financial-indicators',
name: 'FinancialIndicators',
component: () => import('@reports/views/FinancialIndicatorsView.vue'),
meta: { requiresAuth: true, title: 'Indicatori Financiari - ROA2WEB' }
},
{
// US-603: Single route for Detailed Invoices with tabs (Clienți/Furnizori)
path: 'detailed-invoices',

View File

@@ -41,11 +41,12 @@
* Events:
* - item-click: Emitted when a button item (without `to`) is clicked
*
* Default items (4 links):
* Default items (5 links):
* - Dashboard (/dashboard)
* - Bonuri (/data-entry)
* - Detalii (/reports/detailed-invoices)
* - Setări (/settings)
* - Facturi (/reports/detailed-invoices)
* - Banca (/reports/bank)
* - Casa (/reports/cash)
* - Indicatori (/reports/financial-indicators)
*/
defineProps({
@@ -57,9 +58,10 @@ defineProps({
type: Array,
default: () => [
{ to: '/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/data-entry', icon: 'pi pi-shopping-bag', label: 'Bonuri' },
{ to: '/reports/detailed-invoices', icon: 'pi pi-file-edit', label: 'Detalii' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
{ to: '/reports/detailed-invoices', icon: 'pi pi-file-edit', label: 'Facturi' },
{ to: '/reports/bank', icon: 'pi pi-building', label: 'Banca' },
{ to: '/reports/cash', icon: 'pi pi-wallet', label: 'Casa' },
{ to: '/reports/financial-indicators', icon: 'pi pi-chart-bar', label: 'Indicatori' }
],
validator: (items) => {
return Array.isArray(items) && items.every(

View File

@@ -724,9 +724,10 @@ const rapoarteItems = [
{ to: '/reports/bank', icon: 'pi pi-building', label: 'Bancă', exactMatch: true }
]
// ANALIZE: Scadențe
// ANALIZE: Scadențe, Indicatori Financiari
const analizeItems = [
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true }
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true },
{ to: '/reports/financial-indicators', icon: 'pi pi-chart-bar', label: 'Indicatori', exactMatch: true }
]
// ADMINISTRARE: Setări