Newer
Older
import { useState } from 'react'
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, CartesianGrid } from 'recharts'
import { getTimeSeries, type TimeSeriesData } from '../../services/temporal.services'
import { TAX_TYPES, YEARS, COLORS } from '../../constants'
import ErrorDiv from '../molecules/ErrorDiv'
import Loader from '../molecules/Loader'
interface TemporalProps {
regions: string[]
}
export default function Temporal({ regions }: TemporalProps) {
// Selected tax type filter (tfpb, tfpnb, th, cfe)
const [taxType, setTaxType] = useState<string>('tfpb')
// Start year for the time range
const [startYear, setStartYear] = useState<number>(2019)
// End year for the time range
const [endYear, setEndYear] = useState<number>(2022)
// Time series data grouped by region { region: [{year, avg_rate}] }
const [data, setData] = useState<TimeSeriesData | null>(null)
// API request loading state
const [error, setError] = useState<string | null>(null)
// Regions currently selected for display (initialized from SSR props)
const [selectedRegions, setSelectedRegions] = useState<string[]>(regions)
// Toggles a region on/off in the selection
const toggleRegion = (region: string) => {
setSelectedRegions(prev => (prev.includes(region) ? prev.filter(r => r !== region) : [...prev, region]))
}
// Fetches time series data from API for all regions with selected tax type and year range
const handleSubmit = async () => {
setLoading(true)
setError(null)
try {
const result = await getTimeSeries([], taxType, startYear, endYear)
setData(result)
} catch (e) {
setData(null)
setError(e instanceof Error ? e.message : 'Une erreur est survenue')
} finally {
setLoading(false)
}
// Regions present in API response that are also selected by the user
const visibleRegions = data ? Object.keys(data).filter(r => selectedRegions.includes(r)) : []
// Pivoted data for Recharts: [{year, Bretagne: 1.2, Normandie: 1.5, ...}]
? YEARS.filter(y => y >= startYear && y <= endYear).map(year => {
const point: Record<string, number | string> = { year }
for (const region of visibleRegions) {
const entry = data[region]?.find(e => e.year === year)
if (entry) point[region] = parseFloat(entry.avg_rate)
}
return point
})
: []
return (
<div className="p-6 text-white flex flex-col gap-6">
<h2 className="text-lg font-semibold">Taux d'imposition moyen par region</h2>
<div className="flex flex-wrap gap-4 items-end">
<div className="flex flex-col gap-1">
<label className="text-xs text-[#b0afaf] uppercase">Taxe</label>
onChange={e => setTaxType(e.target.value)}
className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
>
<option key={t} value={t}>
{t.toUpperCase()}
</option>
))}
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs text-[#b0afaf] uppercase">De</label>
onChange={e => setStartYear(Number(e.target.value))}
className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
>
<option key={y} value={y}>
{y}
</option>
))}
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs text-[#b0afaf] uppercase">A</label>
onChange={e => setEndYear(Number(e.target.value))}
className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm"
>
<option key={y} value={y}>
{y}
</option>
))}
</select>
</div>
<button
onClick={handleSubmit}
disabled={loading}
className="bg-[#8884d8] hover:bg-[#7773c7] disabled:opacity-50 text-white text-sm px-4 py-1.5 rounded transition-colors"
>
{loading ? 'Chargement...' : 'Afficher'}
</button>
</div>
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
{regions.length > 0 && (
<div className="flex flex-col gap-2">
<label className="text-xs text-[#b0afaf] uppercase">Régions {selectedRegions.length > 0 && `(${selectedRegions.length})`}</label>
<div className="flex flex-wrap gap-2">
<button
onClick={() => setSelectedRegions(regions)}
className="text-xs px-3 py-1 rounded-full border border-[#3a3f44] text-[#b0afaf] hover:text-white transition-colors"
>
Tous
</button>
<button
onClick={() => setSelectedRegions([])}
className="text-xs px-3 py-1 rounded-full border border-[#3a3f44] text-[#b0afaf] hover:text-white transition-colors"
>
Aucun
</button>
{regions.map(region => (
<button
key={region}
onClick={() => toggleRegion(region)}
className={`text-xs px-3 py-1 rounded-full border transition-colors ${
selectedRegions.includes(region)
? 'bg-[#8884d8] border-[#8884d8] text-white'
: 'border-[#3a3f44] text-[#b0afaf] hover:text-white'
}`}
>
{region}
</button>
))}
</div>
</div>
)}
{error && <ErrorDiv message={error} />}
{!loading && !error && chartData.length > 0 && (
<ResponsiveContainer width="100%" height={400}>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#3a3f44" />
<XAxis dataKey="year" stroke="#b0afaf" />
<YAxis stroke="#b0afaf" tickFormatter={(v: number) => `${v}%`} />
content={({ payload, label }) => {
if (!payload || payload.length === 0) return null
return (
<div
className="tooltip-scroll"
style={{
backgroundColor: '#212529',
border: '1px solid #3a3f44',
borderRadius: 8,
padding: '8px 12px',
maxHeight: 300,
overflowY: 'auto',
}}
>
<p style={{ margin: '0 0 4px', color: '#b0afaf' }}>{label}</p>
{[...payload].sort((a, b) => Number(b.value) - Number(a.value)).map(item => (
<p key={item.name} style={{ margin: '2px 0', color: item.color }}>
{item.name} : {item.value}%
</p>
))}
</div>
)
wrapperStyle={{ zIndex: 10, pointerEvents: 'auto' }}
/>
<Legend
wrapperStyle={{
paddingTop: '20px',
}}
<Line
key={region}
type="monotone"
dataKey={region}
stroke={COLORS[regions.indexOf(region) % COLORS.length]}