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:
2026-08-22 17:29:10 +02:00
parent e81cd5ffa4
commit a5a1db9968
2 changed files with 100 additions and 2 deletions

View 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>

View File

@@ -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'