feat: Frontend CSS refactoring and test improvements
Frontend: - Refactored CSS architecture with new utility classes - Updated dashboard components styling - Improved responsive grid system - Enhanced typography and variables - Updated E2E and integration tests Added: - Claude Code slash commands for validation - SSH tunnel and start test scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,493 +44,524 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import { Chart, registerables } from "chart.js";
|
||||
|
||||
Chart.register(...registerables)
|
||||
Chart.register(...registerables);
|
||||
|
||||
const props = defineProps({
|
||||
inflowsValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
outflowsValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
inflowsTrend: {
|
||||
type: Object,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
outflowsTrend: {
|
||||
type: Object,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
inflowsSparkline: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
outflowsSparkline: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
inflowsPreviousSparkline: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
outflowsPreviousSparkline: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
sparklineLabels: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
previousSparklineLabels: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
// Refs pentru 2 canvas-uri separate
|
||||
const inflowsCanvas = ref(null)
|
||||
const outflowsCanvas = ref(null)
|
||||
let inflowsChartInstance = null
|
||||
let outflowsChartInstance = null
|
||||
const inflowsCanvas = ref(null);
|
||||
const outflowsCanvas = ref(null);
|
||||
let inflowsChartInstance = null;
|
||||
let outflowsChartInstance = null;
|
||||
|
||||
// Format currency
|
||||
const formatCurrency = (amount) => {
|
||||
if (!amount && amount !== 0) return '0 RON'
|
||||
return new Intl.NumberFormat('ro-RO', {
|
||||
style: 'currency',
|
||||
currency: 'RON',
|
||||
if (!amount && amount !== 0) return "0 RON";
|
||||
return new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(Math.abs(amount))
|
||||
}
|
||||
maximumFractionDigits: 0,
|
||||
}).format(Math.abs(amount));
|
||||
};
|
||||
|
||||
// Check if sparkline data exists
|
||||
const hasSparklineData = computed(() => {
|
||||
return props.inflowsSparkline.length > 0 && props.outflowsSparkline.length > 0
|
||||
})
|
||||
return (
|
||||
props.inflowsSparkline.length > 0 && props.outflowsSparkline.length > 0
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize Încasări chart
|
||||
const initializeInflowsChart = async () => {
|
||||
if (!inflowsCanvas.value || props.inflowsSparkline.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy existing chart
|
||||
if (inflowsChartInstance) {
|
||||
inflowsChartInstance.destroy()
|
||||
inflowsChartInstance = null
|
||||
inflowsChartInstance.destroy();
|
||||
inflowsChartInstance = null;
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
await nextTick();
|
||||
|
||||
const ctx = inflowsCanvas.value.getContext('2d')
|
||||
const ctx = inflowsCanvas.value.getContext("2d");
|
||||
|
||||
// Generate labels
|
||||
const labels = props.sparklineLabels.length > 0
|
||||
? props.sparklineLabels
|
||||
: props.inflowsSparkline.map((_, i) => `L${i + 1}`)
|
||||
const labels =
|
||||
props.sparklineLabels.length > 0
|
||||
? props.sparklineLabels
|
||||
: props.inflowsSparkline.map((_, i) => `L${i + 1}`);
|
||||
|
||||
// Prepare datasets
|
||||
const datasets = [{
|
||||
label: 'Încasări (curent)',
|
||||
data: props.inflowsSparkline,
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: '#10b981',
|
||||
pointHoverBorderColor: '#ffffff',
|
||||
pointHoverBorderWidth: 2
|
||||
}]
|
||||
const datasets = [
|
||||
{
|
||||
label: "Încasări (curent)",
|
||||
data: props.inflowsSparkline,
|
||||
borderColor: "#10b981",
|
||||
backgroundColor: "rgba(16, 185, 129, 0.1)",
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: "#10b981",
|
||||
pointHoverBorderColor: "#ffffff",
|
||||
pointHoverBorderWidth: 2,
|
||||
},
|
||||
];
|
||||
|
||||
// Add previous year dataset if available
|
||||
if (props.inflowsPreviousSparkline && props.inflowsPreviousSparkline.length > 0) {
|
||||
if (
|
||||
props.inflowsPreviousSparkline &&
|
||||
props.inflowsPreviousSparkline.length > 0
|
||||
) {
|
||||
datasets.push({
|
||||
label: 'Încasări (anul precedent)',
|
||||
label: "Încasări (anul precedent)",
|
||||
data: props.inflowsPreviousSparkline,
|
||||
borderColor: 'rgba(16, 185, 129, 0.4)',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.05)',
|
||||
borderColor: "rgba(16, 185, 129, 0.4)",
|
||||
backgroundColor: "rgba(16, 185, 129, 0.05)",
|
||||
borderWidth: 2,
|
||||
borderDash: [5, 5],
|
||||
fill: false,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: 'rgba(16, 185, 129, 0.4)',
|
||||
pointHoverBorderColor: '#ffffff',
|
||||
pointHoverBorderWidth: 2
|
||||
})
|
||||
pointHoverBackgroundColor: "rgba(16, 185, 129, 0.4)",
|
||||
pointHoverBorderColor: "#ffffff",
|
||||
pointHoverBorderWidth: 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate limits including both datasets
|
||||
const allDataPoints = [...props.inflowsSparkline]
|
||||
if (props.inflowsPreviousSparkline && props.inflowsPreviousSparkline.length > 0) {
|
||||
allDataPoints.push(...props.inflowsPreviousSparkline)
|
||||
const allDataPoints = [...props.inflowsSparkline];
|
||||
if (
|
||||
props.inflowsPreviousSparkline &&
|
||||
props.inflowsPreviousSparkline.length > 0
|
||||
) {
|
||||
allDataPoints.push(...props.inflowsPreviousSparkline);
|
||||
}
|
||||
const dataMin = Math.min(...allDataPoints)
|
||||
const dataMax = Math.max(...allDataPoints)
|
||||
const dataRange = dataMax - dataMin
|
||||
const dataMean = allDataPoints.reduce((sum, val) => sum + val, 0) / allDataPoints.length
|
||||
const dataMin = Math.min(...allDataPoints);
|
||||
const dataMax = Math.max(...allDataPoints);
|
||||
const dataRange = dataMax - dataMin;
|
||||
const dataMean =
|
||||
allDataPoints.reduce((sum, val) => sum + val, 0) / allDataPoints.length;
|
||||
|
||||
// CORECT: Forțăm range minim SIMETRIC, apoi adăugăm padding
|
||||
const minVisibleRange = dataMean * 0.25 // 25% din medie = range minim vizibil
|
||||
const center = (dataMin + dataMax) / 2
|
||||
const targetRange = Math.max(dataRange, minVisibleRange)
|
||||
const minVisibleRange = dataMean * 0.25; // 25% din medie = range minim vizibil
|
||||
const center = (dataMin + dataMax) / 2;
|
||||
const targetRange = Math.max(dataRange, minVisibleRange);
|
||||
|
||||
// Calculează limite simetric față de centru
|
||||
let calculatedMin = center - targetRange / 2
|
||||
let calculatedMax = center + targetRange / 2
|
||||
let calculatedMin = center - targetRange / 2;
|
||||
let calculatedMax = center + targetRange / 2;
|
||||
|
||||
// Adaugă padding PESTE range-ul asigurat (nu înăuntru!)
|
||||
const paddingAmount = targetRange * 0.10 // 10% padding suplimentar
|
||||
const paddingAmount = targetRange * 0.1; // 10% padding suplimentar
|
||||
|
||||
// Aplică limitele finale (nu permite negative dacă toate datele sunt pozitive)
|
||||
const allPositive = dataMin >= 0
|
||||
const yMin = allPositive ? Math.max(0, calculatedMin - paddingAmount) : calculatedMin - paddingAmount
|
||||
const yMax = calculatedMax + paddingAmount
|
||||
const allPositive = dataMin >= 0;
|
||||
const yMin = allPositive
|
||||
? Math.max(0, calculatedMin - paddingAmount)
|
||||
: calculatedMin - paddingAmount;
|
||||
const yMax = calculatedMax + paddingAmount;
|
||||
|
||||
inflowsChartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets
|
||||
datasets: datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
mode: "index",
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: datasets.length > 1,
|
||||
position: 'top',
|
||||
align: 'end',
|
||||
position: "top",
|
||||
align: "end",
|
||||
labels: {
|
||||
boxWidth: 12,
|
||||
boxHeight: 12,
|
||||
padding: 8,
|
||||
font: {
|
||||
size: 10,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
color: 'rgba(107, 114, 128, 0.9)',
|
||||
color: "rgba(107, 114, 128, 0.9)",
|
||||
usePointStyle: true,
|
||||
pointStyle: 'line'
|
||||
}
|
||||
pointStyle: "line",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleColor: '#ffffff',
|
||||
bodyColor: '#ffffff',
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||
titleColor: "#ffffff",
|
||||
bodyColor: "#ffffff",
|
||||
borderColor: "rgba(255, 255, 255, 0.2)",
|
||||
borderWidth: 1,
|
||||
cornerRadius: 6,
|
||||
displayColors: true,
|
||||
callbacks: {
|
||||
title: (context) => context[0].label || '',
|
||||
title: (context) => context[0].label || "",
|
||||
label: (context) => {
|
||||
const value = context.parsed.y
|
||||
const label = context.dataset.label || ''
|
||||
const formattedValue = new Intl.NumberFormat('ro-RO', {
|
||||
style: 'currency',
|
||||
currency: 'RON',
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(value)
|
||||
return `${label}: ${formattedValue}`
|
||||
}
|
||||
}
|
||||
}
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
return `${label}: ${formattedValue}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
color: 'rgba(107, 114, 128, 0.7)',
|
||||
color: "rgba(107, 114, 128, 0.7)",
|
||||
font: {
|
||||
size: 10,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
maxTicksLimit: 6
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
min: yMin,
|
||||
max: yMax,
|
||||
grid: {
|
||||
color: 'rgba(107, 114, 128, 0.1)',
|
||||
drawBorder: false
|
||||
color: "rgba(107, 114, 128, 0.1)",
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
color: '#10b981',
|
||||
color: "#10b981",
|
||||
font: {
|
||||
size: 11,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
maxTicksLimit: 3,
|
||||
callback: function(value) {
|
||||
callback: function (value) {
|
||||
if (value >= 1000000) {
|
||||
return (value / 1000000).toFixed(1) + 'M'
|
||||
return (value / 1000000).toFixed(1) + "M";
|
||||
} else if (value >= 1000) {
|
||||
return (value / 1000).toFixed(0) + 'k'
|
||||
return (value / 1000).toFixed(0) + "k";
|
||||
}
|
||||
return value.toFixed(0)
|
||||
}
|
||||
return value.toFixed(0);
|
||||
},
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize Plăți chart
|
||||
const initializeOutflowsChart = async () => {
|
||||
if (!outflowsCanvas.value || props.outflowsSparkline.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy existing chart
|
||||
if (outflowsChartInstance) {
|
||||
outflowsChartInstance.destroy()
|
||||
outflowsChartInstance = null
|
||||
outflowsChartInstance.destroy();
|
||||
outflowsChartInstance = null;
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
await nextTick();
|
||||
|
||||
const ctx = outflowsCanvas.value.getContext('2d')
|
||||
const ctx = outflowsCanvas.value.getContext("2d");
|
||||
|
||||
// Generate labels
|
||||
const labels = props.sparklineLabels.length > 0
|
||||
? props.sparklineLabels
|
||||
: props.outflowsSparkline.map((_, i) => `L${i + 1}`)
|
||||
const labels =
|
||||
props.sparklineLabels.length > 0
|
||||
? props.sparklineLabels
|
||||
: props.outflowsSparkline.map((_, i) => `L${i + 1}`);
|
||||
|
||||
// Prepare datasets
|
||||
const datasets = [{
|
||||
label: 'Plăți (curent)',
|
||||
data: props.outflowsSparkline,
|
||||
borderColor: '#ef4444',
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: '#ef4444',
|
||||
pointHoverBorderColor: '#ffffff',
|
||||
pointHoverBorderWidth: 2
|
||||
}]
|
||||
const datasets = [
|
||||
{
|
||||
label: "Plăți (curent)",
|
||||
data: props.outflowsSparkline,
|
||||
borderColor: "#ef4444",
|
||||
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: "#ef4444",
|
||||
pointHoverBorderColor: "#ffffff",
|
||||
pointHoverBorderWidth: 2,
|
||||
},
|
||||
];
|
||||
|
||||
// Add previous year dataset if available
|
||||
if (props.outflowsPreviousSparkline && props.outflowsPreviousSparkline.length > 0) {
|
||||
if (
|
||||
props.outflowsPreviousSparkline &&
|
||||
props.outflowsPreviousSparkline.length > 0
|
||||
) {
|
||||
datasets.push({
|
||||
label: 'Plăți (anul precedent)',
|
||||
label: "Plăți (anul precedent)",
|
||||
data: props.outflowsPreviousSparkline,
|
||||
borderColor: 'rgba(239, 68, 68, 0.4)',
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.05)',
|
||||
borderColor: "rgba(239, 68, 68, 0.4)",
|
||||
backgroundColor: "rgba(239, 68, 68, 0.05)",
|
||||
borderWidth: 2,
|
||||
borderDash: [5, 5],
|
||||
fill: false,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: 'rgba(239, 68, 68, 0.4)',
|
||||
pointHoverBorderColor: '#ffffff',
|
||||
pointHoverBorderWidth: 2
|
||||
})
|
||||
pointHoverBackgroundColor: "rgba(239, 68, 68, 0.4)",
|
||||
pointHoverBorderColor: "#ffffff",
|
||||
pointHoverBorderWidth: 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate limits including both datasets
|
||||
const allDataPoints = [...props.outflowsSparkline]
|
||||
if (props.outflowsPreviousSparkline && props.outflowsPreviousSparkline.length > 0) {
|
||||
allDataPoints.push(...props.outflowsPreviousSparkline)
|
||||
const allDataPoints = [...props.outflowsSparkline];
|
||||
if (
|
||||
props.outflowsPreviousSparkline &&
|
||||
props.outflowsPreviousSparkline.length > 0
|
||||
) {
|
||||
allDataPoints.push(...props.outflowsPreviousSparkline);
|
||||
}
|
||||
const dataMin = Math.min(...allDataPoints)
|
||||
const dataMax = Math.max(...allDataPoints)
|
||||
const dataRange = dataMax - dataMin
|
||||
const dataMean = allDataPoints.reduce((sum, val) => sum + val, 0) / allDataPoints.length
|
||||
const dataMin = Math.min(...allDataPoints);
|
||||
const dataMax = Math.max(...allDataPoints);
|
||||
const dataRange = dataMax - dataMin;
|
||||
const dataMean =
|
||||
allDataPoints.reduce((sum, val) => sum + val, 0) / allDataPoints.length;
|
||||
|
||||
// CORECT: Forțăm range minim SIMETRIC, apoi adăugăm padding
|
||||
const minVisibleRange = dataMean * 0.25 // 25% din medie = range minim vizibil
|
||||
const center = (dataMin + dataMax) / 2
|
||||
const targetRange = Math.max(dataRange, minVisibleRange)
|
||||
const minVisibleRange = dataMean * 0.25; // 25% din medie = range minim vizibil
|
||||
const center = (dataMin + dataMax) / 2;
|
||||
const targetRange = Math.max(dataRange, minVisibleRange);
|
||||
|
||||
// Calculează limite simetric față de centru
|
||||
let calculatedMin = center - targetRange / 2
|
||||
let calculatedMax = center + targetRange / 2
|
||||
let calculatedMin = center - targetRange / 2;
|
||||
let calculatedMax = center + targetRange / 2;
|
||||
|
||||
// Adaugă padding PESTE range-ul asigurat (nu înăuntru!)
|
||||
const paddingAmount = targetRange * 0.10 // 10% padding suplimentar
|
||||
const paddingAmount = targetRange * 0.1; // 10% padding suplimentar
|
||||
|
||||
// Aplică limitele finale (nu permite negative dacă toate datele sunt pozitive)
|
||||
const allPositive = dataMin >= 0
|
||||
const yMin = allPositive ? Math.max(0, calculatedMin - paddingAmount) : calculatedMin - paddingAmount
|
||||
const yMax = calculatedMax + paddingAmount
|
||||
const allPositive = dataMin >= 0;
|
||||
const yMin = allPositive
|
||||
? Math.max(0, calculatedMin - paddingAmount)
|
||||
: calculatedMin - paddingAmount;
|
||||
const yMax = calculatedMax + paddingAmount;
|
||||
|
||||
outflowsChartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets
|
||||
datasets: datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
mode: "index",
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: datasets.length > 1,
|
||||
position: 'top',
|
||||
align: 'end',
|
||||
position: "top",
|
||||
align: "end",
|
||||
labels: {
|
||||
boxWidth: 12,
|
||||
boxHeight: 12,
|
||||
padding: 8,
|
||||
font: {
|
||||
size: 10,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
color: 'rgba(107, 114, 128, 0.9)',
|
||||
color: "rgba(107, 114, 128, 0.9)",
|
||||
usePointStyle: true,
|
||||
pointStyle: 'line'
|
||||
}
|
||||
pointStyle: "line",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleColor: '#ffffff',
|
||||
bodyColor: '#ffffff',
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||
titleColor: "#ffffff",
|
||||
bodyColor: "#ffffff",
|
||||
borderColor: "rgba(255, 255, 255, 0.2)",
|
||||
borderWidth: 1,
|
||||
cornerRadius: 6,
|
||||
displayColors: true,
|
||||
callbacks: {
|
||||
title: (context) => context[0].label || '',
|
||||
title: (context) => context[0].label || "",
|
||||
label: (context) => {
|
||||
const value = context.parsed.y
|
||||
const label = context.dataset.label || ''
|
||||
const formattedValue = new Intl.NumberFormat('ro-RO', {
|
||||
style: 'currency',
|
||||
currency: 'RON',
|
||||
const value = context.parsed.y;
|
||||
const label = context.dataset.label || "";
|
||||
const formattedValue = new Intl.NumberFormat("ro-RO", {
|
||||
style: "currency",
|
||||
currency: "RON",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(value)
|
||||
return `${label}: ${formattedValue}`
|
||||
}
|
||||
}
|
||||
}
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
return `${label}: ${formattedValue}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
color: 'rgba(107, 114, 128, 0.7)',
|
||||
color: "rgba(107, 114, 128, 0.7)",
|
||||
font: {
|
||||
size: 10,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
maxTicksLimit: 6
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
min: yMin,
|
||||
max: yMax,
|
||||
grid: {
|
||||
color: 'rgba(107, 114, 128, 0.1)',
|
||||
drawBorder: false
|
||||
color: "rgba(107, 114, 128, 0.1)",
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
color: '#ef4444',
|
||||
color: "#ef4444",
|
||||
font: {
|
||||
size: 11,
|
||||
family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif'
|
||||
family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
},
|
||||
maxTicksLimit: 3,
|
||||
callback: function(value) {
|
||||
callback: function (value) {
|
||||
if (value >= 1000000) {
|
||||
return (value / 1000000).toFixed(1) + 'M'
|
||||
return (value / 1000000).toFixed(1) + "M";
|
||||
} else if (value >= 1000) {
|
||||
return (value / 1000).toFixed(0) + 'k'
|
||||
return (value / 1000).toFixed(0) + "k";
|
||||
}
|
||||
return value.toFixed(0)
|
||||
}
|
||||
return value.toFixed(0);
|
||||
},
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Watch for data changes
|
||||
watch(() => [
|
||||
props.inflowsSparkline,
|
||||
props.outflowsSparkline,
|
||||
props.sparklineLabels,
|
||||
props.inflowsPreviousSparkline,
|
||||
props.outflowsPreviousSparkline,
|
||||
props.previousSparklineLabels
|
||||
], async () => {
|
||||
await Promise.all([
|
||||
initializeInflowsChart(),
|
||||
initializeOutflowsChart()
|
||||
])
|
||||
}, { deep: true })
|
||||
watch(
|
||||
() => [
|
||||
props.inflowsSparkline,
|
||||
props.outflowsSparkline,
|
||||
props.sparklineLabels,
|
||||
props.inflowsPreviousSparkline,
|
||||
props.outflowsPreviousSparkline,
|
||||
props.previousSparklineLabels,
|
||||
],
|
||||
async () => {
|
||||
await Promise.all([initializeInflowsChart(), initializeOutflowsChart()]);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
initializeInflowsChart(),
|
||||
initializeOutflowsChart()
|
||||
])
|
||||
})
|
||||
await Promise.all([initializeInflowsChart(), initializeOutflowsChart()]);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (inflowsChartInstance) {
|
||||
inflowsChartInstance.destroy()
|
||||
inflowsChartInstance = null
|
||||
inflowsChartInstance.destroy();
|
||||
inflowsChartInstance = null;
|
||||
}
|
||||
if (outflowsChartInstance) {
|
||||
outflowsChartInstance.destroy()
|
||||
outflowsChartInstance = null
|
||||
outflowsChartInstance.destroy();
|
||||
outflowsChartInstance = null;
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user