Cartes, en-tetes, champs de filtre, tableaux, badges, paginations et indicateurs de chargement des tableaux de bord etaient proches mais jamais identiques (bordure presente ou absente, paddings et arrondis variables). Ils utilisent maintenant les classes communes. Les colonnes tronquees des tableaux sont elargies et les sept filtres des depenses tiennent sur une ligne, la largeur retrouvee le permettant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
2.2 KiB
Vue
108 lines
2.2 KiB
Vue
<template>
|
|
<div class="card card-body">
|
|
<h3 class="card-title block mb-4">Evolution mensuelle</h3>
|
|
<div class="h-64">
|
|
<Bar
|
|
v-if="chartData.labels.length > 0"
|
|
:data="chartData"
|
|
:options="chartOptions"
|
|
/>
|
|
<div v-else class="h-full flex items-center justify-center text-gray-500 text-sm">
|
|
Aucune donnee
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import { Bar } from 'vue-chartjs'
|
|
import {
|
|
Chart as ChartJS,
|
|
CategoryScale,
|
|
LinearScale,
|
|
BarElement,
|
|
Title,
|
|
Tooltip,
|
|
Legend
|
|
} from 'chart.js'
|
|
|
|
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
|
|
|
|
const props = defineProps({
|
|
data: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
})
|
|
|
|
const monthNames = [
|
|
'Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Juin',
|
|
'Juil', 'Aout', 'Sep', 'Oct', 'Nov', 'Dec'
|
|
]
|
|
|
|
const chartData = computed(() => {
|
|
const items = props.data.slice(-24) // Last 24 months
|
|
return {
|
|
labels: items.map(d => `${monthNames[d.month - 1]} ${d.year}`),
|
|
datasets: [
|
|
{
|
|
label: 'Debit',
|
|
data: items.map(d => d.total_debit),
|
|
backgroundColor: '#EF4444',
|
|
borderRadius: 4
|
|
},
|
|
{
|
|
label: 'Credit',
|
|
data: items.map(d => d.total_credit),
|
|
backgroundColor: '#10B981',
|
|
borderRadius: 4
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
const chartOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: {
|
|
color: '#9CA3AF',
|
|
font: { size: 11 },
|
|
boxWidth: 12,
|
|
padding: 15
|
|
}
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: (ctx) => {
|
|
const value = new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(ctx.raw)
|
|
return ` ${ctx.dataset.label}: ${value}`
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: { color: '#6B7280', font: { size: 10 } },
|
|
grid: { color: '#374151' }
|
|
},
|
|
y: {
|
|
ticks: {
|
|
color: '#6B7280',
|
|
callback: (value) => {
|
|
if (value >= 1000) return `${(value / 1000).toFixed(0)}k`
|
|
return value
|
|
}
|
|
},
|
|
grid: { color: '#374151' }
|
|
}
|
|
}
|
|
}
|
|
</script>
|