setSidebarOpen(false)}
- />
- )}
+ {sidebarOpen &&
setSidebarOpen(false)} />}
diff --git a/pwa/app/components/tabs/diagram.tsx b/pwa/app/components/tabs/diagram.tsx
index c9df2e01c20bdb5663ef46c64bdf11df03aae6e7..7485ff3d9d05243aa4cfe9ccd9458319d096902e 100644
--- a/pwa/app/components/tabs/diagram.tsx
+++ b/pwa/app/components/tabs/diagram.tsx
@@ -1,120 +1,163 @@
'use client'
import { useState, useEffect } from 'react'
-import { Doughnut } from 'react-chartjs-2'
-import { Chart as ChartJS, ArcElement, Tooltip, Legend, TooltipItem } from 'chart.js'
-import ChartDataLabels from 'chartjs-plugin-datalabels'
+import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recharts'
import { TAX_TYPES, YEARS, COLORS } from '../../constants'
-import { getRegionDistribution, prepareChartData, RegionDistribution } from '../../services/diagram.services'
+import { getRegionDistribution, RegionDistribution } from '../../services/diagram.services'
import ErrorDiv from '../molecules/ErrorDiv'
+import RegionSelector from '../molecules/RegionSelector'
-ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels)
+interface DiagramProps {
+ regions: string[]
+}
-export default function Diagram() {
- const [taxType, setTaxType] = useState('th')
- const [year, setYear] = useState(2019)
- const [topCount, setTopCount] = useState(9)
+export default function Diagram({ regions }: DiagramProps) {
+ const [taxType, setTaxType] = useState('')
+ const [year, setYear] = useState
(null)
const [data, setData] = useState([])
- const [maxRegions, setMaxRegions] = useState(20)
+ const [rawData, setRawData] = useState([]) // Store fetched data
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
+ // Regions currently selected for display (all selected by default)
+ const [selectedRegions, setSelectedRegions] = useState([])
+ const [isInitialized, setIsInitialized] = useState(false)
- // Load number of regions on mount
+ // Initialize selectedRegions with all regions on first render
useEffect(() => {
- const loadMaxRegions = async () => {
+ if (!isInitialized && regions.length > 0) {
+ setSelectedRegions(regions)
+ setIsInitialized(true)
+ }
+ }, [regions, isInitialized])
+
+ // Fetch data only when tax type or year changes
+ useEffect(() => {
+ const fetchData = async () => {
+ if (!taxType || !year) {
+ setRawData([])
+ setData([])
+ return
+ }
+ setLoading(true)
+ setError(null)
try {
const result = await getRegionDistribution(taxType, year)
- setMaxRegions(result.length)
- } catch {
- // Keep default value on error
+ setRawData(result)
+ } catch (e) {
+ setRawData([])
+ setData([])
+ setError(e instanceof Error ? e.message : 'Une erreur est survenue')
+ } finally {
+ setLoading(false)
}
}
- loadMaxRegions()
+
+ fetchData()
}, [taxType, year])
- const handleSubmit = async () => {
- setLoading(true)
- setError(null)
- try {
- const result = await getRegionDistribution(taxType, year)
- setMaxRegions(result.length)
- const chartData = prepareChartData(result, topCount)
- setData(chartData)
- } catch (e) {
+ // Filter and process data when selectedRegions or rawData changes
+ useEffect(() => {
+ if (rawData.length === 0 || selectedRegions.length === 0) {
setData([])
- setError(e instanceof Error ? e.message : 'Une erreur est survenue')
- } finally {
- setLoading(false)
+ return
}
- }
- const chartConfig = {
- labels: data.map(d => d.region),
- datasets: [
- {
- data: data.map(d => d.total_amount),
- backgroundColor: COLORS,
- borderColor: '#1a1d21',
- borderWidth: 2,
- },
- ],
+ // Calculate total for percentages
+ const totalAmount = rawData.reduce((sum, item) => sum + item.total_amount, 0)
+
+ // Separate selected and non-selected regions
+ const selected = rawData.filter(r => selectedRegions.includes(r.region))
+ const notSelected = rawData.filter(r => !selectedRegions.includes(r.region))
+
+ // Filter selected regions: keep only those >= 0.5%, others go to "Autres"
+ const significantRegions: RegionDistribution[] = []
+ const smallRegions: RegionDistribution[] = []
+
+ selected.forEach(item => {
+ const percentage = totalAmount > 0 ? (item.total_amount / totalAmount) * 100 : 0
+ if (percentage >= 0.5) {
+ significantRegions.push({
+ region: item.region,
+ total_amount: item.total_amount,
+ percentage: percentage,
+ })
+ } else {
+ smallRegions.push(item)
+ }
+ })
+
+ // Combine small selected regions with non-selected regions for "Autres"
+ const othersRegions = [...smallRegions, ...notSelected]
+
+ // Add "Autres" if there are regions to group
+ if (othersRegions.length > 0) {
+ const othersTotal = othersRegions.reduce((sum, item) => sum + item.total_amount, 0)
+ const othersPercentage = totalAmount > 0 ? (othersTotal / totalAmount) * 100 : 0
+ significantRegions.push({
+ region: 'Autres',
+ total_amount: othersTotal,
+ percentage: othersPercentage,
+ otherRegionsSmall: smallRegions.map(item => ({
+ name: item.region,
+ percentage: totalAmount > 0 ? (item.total_amount / totalAmount) * 100 : 0,
+ })),
+ otherRegionsNotSelected: notSelected.map(item => ({
+ name: item.region,
+ percentage: totalAmount > 0 ? (item.total_amount / totalAmount) * 100 : 0,
+ })),
+ })
+ }
+
+ setData(significantRegions)
+ }, [rawData, selectedRegions])
+
+ // Custom label to display percentage on slices
+ const renderLabel = (props: { payload?: RegionDistribution; percent?: number }) => {
+ const entry = props.payload
+ if (!entry || !entry.percentage || entry.percentage < 3) return ''
+ return `${entry.percentage.toFixed(1)}%`
}
- const chartOptions = {
- responsive: true,
- maintainAspectRatio: true,
- aspectRatio: 1.5,
- plugins: {
- legend: {
- position: 'right' as const,
- labels: {
- color: '#b0afaf',
- padding: 25,
- font: { size: 18 },
- boxWidth: 30,
- boxHeight: 30,
- },
- },
- tooltip: {
- backgroundColor: '#212529',
- borderColor: '#3a3f44',
- borderWidth: 1,
- titleFont: { size: 18 },
- bodyFont: { size: 16 },
- padding: 12,
- callbacks: {
- label: (ctx: TooltipItem<'doughnut'>) => {
- const region = data[ctx.dataIndex]
- if (!region) return ''
- const amount = region.total_amount ? (region.total_amount / 1_000_000).toFixed(2) : '0.00'
- const percentage = region.percentage ? region.percentage.toFixed(2) : '0.00'
-
- const lines = [`${region.region}`, `Volume: ${amount} M€`, `Part: ${percentage}%`]
-
- // If it's "Others", add the list of regions
- if (region.region === 'Autres' && region.otherRegions) {
- lines.push('', 'Régions incluses:')
- region.otherRegions.forEach((r: string) => lines.push(` • ${r}`))
- }
-
- return lines
- },
- },
- },
- datalabels: {
- color: '#fff',
- font: {
- weight: 'bold' as const,
- size: 24,
- },
- formatter: (_value: unknown, ctx: { dataIndex: number }) => {
- const region = data[ctx.dataIndex]
- if (!region || !region.percentage) return ''
- // Only display percentage if greater than 3%
- return region.percentage >= 3 ? `${region.percentage.toFixed(1)}%` : ''
- },
- },
- },
+ // Custom tooltip
+ const CustomTooltip = ({ active, payload }: { active?: boolean; payload?: { payload: RegionDistribution }[] }) => {
+ if (active && payload && payload.length && payload[0]) {
+ const region = payload[0].payload
+ const amount = region.total_amount ? (region.total_amount / 1_000_000).toFixed(2) : '0.00'
+ const percentage = region.percentage ? region.percentage.toFixed(2) : '0.00'
+
+ return (
+
+
{region.region}
+
Volume: {amount} M€
+
Part: {percentage}%
+ {region.region === 'Autres' && (
+ <>
+ {region.otherRegionsSmall && region.otherRegionsSmall.length > 0 && (
+ <>
+
Régions sélectionnées (< 0.5%):
+ {region.otherRegionsSmall.map((r: { name: string; percentage: number }) => (
+
+ • {r.name} ({r.percentage < 0.01 ? '<0.01%' : `${r.percentage.toFixed(2)}%`})
+
+ ))}
+ >
+ )}
+ {region.otherRegionsNotSelected && region.otherRegionsNotSelected.length > 0 && (
+ <>
+
Régions non sélectionnées:
+ {region.otherRegionsNotSelected.map((r: { name: string; percentage: number }) => (
+
+ • {r.name} ({r.percentage < 0.01 ? '<0.01%' : `${r.percentage.toFixed(2)}%`})
+
+ ))}
+ >
+ )}
+ >
+ )}
+
+ )
+ }
+ return null
}
return (
@@ -129,6 +172,7 @@ export default function Diagram() {
onChange={e => setTaxType(e.target.value)}
className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
>
+ -- Sélectionnez --
{TAX_TYPES.map(t => (
{t.toUpperCase()}
@@ -140,10 +184,11 @@ export default function Diagram() {
Année
setYear(Number(e.target.value))}
+ value={year ?? ''}
+ onChange={e => setYear(e.target.value ? Number(e.target.value) : null)}
className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
>
+ -- Sélectionnez --
{YEARS.map(y => (
{y}
@@ -151,38 +196,56 @@ export default function Diagram() {
))}
-
-
- Nombre de régions
- setTopCount(Number(e.target.value))}
- className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
- >
- {Array.from({ length: maxRegions - 4 }, (_, i) => i + 5).map(n => (
-
- {n}
-
- ))}
-
-
-
-
- {loading ? 'Chargement...' : 'Afficher'}
-
}
- {!error && data.length > 0 && (
+ {loading && !error && (
+
+ )}
+
+ {!error && !loading && data.length > 0 && (