feat: add analytics
This commit is contained in:
107
frontend/src/components/analytics/MonthlyChart.vue
Normal file
107
frontend/src/components/analytics/MonthlyChart.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="bg-gray-900 rounded-lg p-4">
|
||||
<h3 class="text-sm font-medium text-gray-300 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>
|
||||
Reference in New Issue
Block a user