feat: montre la répartition des dépenses par tag
Le résumé calculait déjà `by_tag`, la page Dépenses n'en faisait rien : on lisait la dépense par catégorie comptable, jamais par poste. Les deux répartitions se placent côte à côte — même total, deux découpages — et l'évolution mensuelle prend toute la largeur, un axe de temps serré étant le premier à devenir illisible. Les dépenses sans tag y gardent leur part, en gris : c'est le travail de tagging qui reste à faire, pas un trou dans le graphique. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
93
frontend/src/components/analytics/TagChart.vue
Normal file
93
frontend/src/components/analytics/TagChart.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card card-body">
|
||||||
|
<h3 class="card-title block mb-4">Repartition par tag</h3>
|
||||||
|
<div class="h-64">
|
||||||
|
<Doughnut
|
||||||
|
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 { Doughnut } from 'vue-chartjs'
|
||||||
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'#3B82F6', // blue
|
||||||
|
'#EF4444', // red
|
||||||
|
'#10B981', // green
|
||||||
|
'#F59E0B', // amber
|
||||||
|
'#8B5CF6', // purple
|
||||||
|
'#EC4899', // pink
|
||||||
|
'#06B6D4', // cyan
|
||||||
|
'#84CC16', // lime
|
||||||
|
'#F97316', // orange
|
||||||
|
'#6366F1', // indigo
|
||||||
|
]
|
||||||
|
|
||||||
|
/** Gris pour les depenses sans tag : elles restent visibles - c'est le
|
||||||
|
travail de tagging qui reste a faire - sans se disputer une couleur
|
||||||
|
avec les postes reels. */
|
||||||
|
const COULEUR_SANS_TAG = '#6B7280'
|
||||||
|
|
||||||
|
const parts = computed(() => props.data.filter((p) => (p.total_debit ?? 0) > 0).slice(0, 10))
|
||||||
|
|
||||||
|
const chartData = computed(() => ({
|
||||||
|
labels: parts.value.map((p) => tronquer(p.tag_nom || 'Non taggue')),
|
||||||
|
datasets: [{
|
||||||
|
data: parts.value.map((p) => p.total_debit),
|
||||||
|
backgroundColor: parts.value.map((p, i) =>
|
||||||
|
p.tag_id === null || p.tag_id === undefined ? COULEUR_SANS_TAG : colors[i % colors.length]
|
||||||
|
),
|
||||||
|
borderColor: '#1F2937',
|
||||||
|
borderWidth: 2
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right',
|
||||||
|
labels: {
|
||||||
|
color: '#9CA3AF',
|
||||||
|
font: { size: 11 },
|
||||||
|
boxWidth: 12,
|
||||||
|
padding: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (ctx) => {
|
||||||
|
const value = new Intl.NumberFormat('fr-FR', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'EUR'
|
||||||
|
}).format(ctx.raw)
|
||||||
|
return ` ${value}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tronquer(nom) {
|
||||||
|
return nom.substring(0, 25)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -21,12 +21,16 @@
|
|||||||
<!-- KPI Cards -->
|
<!-- KPI Cards -->
|
||||||
<KpiCards :summary="summary" />
|
<KpiCards :summary="summary" />
|
||||||
|
|
||||||
<!-- Charts Row -->
|
<!-- Les deux repartitions cote a cote : meme total, deux decoupages. -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<CategoryChart :data="summary.by_category || []" />
|
<CategoryChart :data="summary.by_category || []" />
|
||||||
<MonthlyChart :data="summary.by_month || []" />
|
<TagChart :data="summary.by_tag || []" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- L'evolution mensuelle prend toute la largeur : un axe de temps
|
||||||
|
serre est le premier a devenir illisible. -->
|
||||||
|
<MonthlyChart :data="summary.by_month || []" />
|
||||||
|
|
||||||
<!-- Top Fournisseurs -->
|
<!-- Top Fournisseurs -->
|
||||||
<TopFournisseursChart :data="summary.by_fournisseur || []" />
|
<TopFournisseursChart :data="summary.by_fournisseur || []" />
|
||||||
|
|
||||||
@@ -45,6 +49,7 @@ import { ref, onMounted } from 'vue'
|
|||||||
import FilterPanel from '../components/analytics/FilterPanel.vue'
|
import FilterPanel from '../components/analytics/FilterPanel.vue'
|
||||||
import KpiCards from '../components/analytics/KpiCards.vue'
|
import KpiCards from '../components/analytics/KpiCards.vue'
|
||||||
import CategoryChart from '../components/analytics/CategoryChart.vue'
|
import CategoryChart from '../components/analytics/CategoryChart.vue'
|
||||||
|
import TagChart from '../components/analytics/TagChart.vue'
|
||||||
import MonthlyChart from '../components/analytics/MonthlyChart.vue'
|
import MonthlyChart from '../components/analytics/MonthlyChart.vue'
|
||||||
import TopFournisseursChart from '../components/analytics/TopFournisseursChart.vue'
|
import TopFournisseursChart from '../components/analytics/TopFournisseursChart.vue'
|
||||||
import DepensesTable from '../components/analytics/DepensesTable.vue'
|
import DepensesTable from '../components/analytics/DepensesTable.vue'
|
||||||
|
|||||||
Reference in New Issue
Block a user