'use client' import { useEffect, useRef, useState } from 'react' import { Scatter } from 'react-chartjs-2' import { Chart as ChartJS, LinearScale, PointElement, Tooltip, Legend } from 'chart.js' ChartJS.register(LinearScale, PointElement, Tooltip, Legend) import { searchDepartments, getCorrelation, type Department, type CorrelationPoint } from '../../services/points.services' import { TAX_TYPES, YEARS } from '../../constants' import ErrorDiv from '../molecules/ErrorDiv' export default function Points() { const [results, setResults] = useState([]) const [departmentCode, setDepartmentCode] = useState('') const [search, setSearch] = useState('') const [showDropdown, setShowDropdown] = useState(false) const [taxType, setTaxType] = useState('th') const [year, setYear] = useState(2019) const [data, setData] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const dropdownRef = useRef(null) const debounceRef = useRef>(null) const [selectedDept, setSelectedDept] = useState(null) useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setShowDropdown(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const handleSearchChange = (value: string) => { setSearch(value) setDepartmentCode('') setShowDropdown(true) if (debounceRef.current) clearTimeout(debounceRef.current) if (value.length < 2) { setResults([]) return } debounceRef.current = setTimeout(() => { searchDepartments(value).then(setResults) }, 300) } const selectDepartment = (dept: Department) => { setDepartmentCode(dept.departmentCode) setSelectedDept(dept) setSearch(`${dept.departmentCode} - ${dept.departmentName}`) setShowDropdown(false) } const handleSubmit = async () => { if (!departmentCode) return setLoading(true) setError(null) try { const result = await getCorrelation(departmentCode, taxType, year) setData(result) } catch (e) { setData([]) setError(e instanceof Error ? e.message : 'Une erreur est survenue') } finally { setLoading(false) } } const chartConfig = { datasets: [ { label: 'Communes', data: data.map(p => ({ x: p.rate, y: p.amount / 1_000_000, commune: p.commune_name })), borderColor: 'rgba(136, 132, 216, 0.8)', backgroundColor: 'rgba(136, 132, 216, 0.8)', pointStyle: 'cross' as const, pointRadius: 4, borderWidth: 2, }, ], } const chartOptions = { responsive: true, maintainAspectRatio: false, scales: { x: { title: { display: true, text: 'Taux (%)', color: '#b0afaf' }, ticks: { color: '#b0afaf' }, grid: { color: '#3a3f44' }, }, y: { title: { display: true, text: 'Volume (M€)', color: '#b0afaf' }, ticks: { color: '#b0afaf' }, grid: { color: '#3a3f44' }, }, }, plugins: { legend: { display: false }, tooltip: { backgroundColor: '#212529', borderColor: '#3a3f44', borderWidth: 1, callbacks: { label: (ctx: { raw: unknown; parsed: { x: number | null; y: number | null } }) => { const raw = ctx.raw as { commune?: string } | undefined const name = raw?.commune ?? '' return [`${name}`, `Taux: ${ctx.parsed.x}%`, `Volume: ${(ctx.parsed.y ?? 0).toFixed(3)} M€`] }, }, }, }, } return (

Relation taux d'imposition / volume collecté

handleSearchChange(e.target.value)} onFocus={() => search.length >= 2 && setShowDropdown(true)} placeholder="Code ou nom..." className="bg-[#212529] border border-[#3a3f44] rounded px-3 py-1.5 text-white text-sm w-64" /> {showDropdown && results.length > 0 && (
{results.map(d => ( ))}
)}
{error && } {!error && data.length > 0 && ( <>

{selectedDept?.departmentName} — {data.length} communes

)}
) }