import { useState, useEffect, useRef } from "react"; import { Box, Grid, GridItem, Text, Flex, Spinner, SimpleGrid, RangeSlider, RangeSliderTrack, RangeSliderFilledTrack, RangeSliderThumb, } from "@chakra-ui/react"; import Select from "react-select"; import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, RadialLinearScale, } from "chart.js"; import { Bar, Pie, Doughnut, Line, Radar, PolarArea } from "react-chartjs-2"; import type { FilterState, AvailableFilters } from "../hooks/types"; // register chart components ChartJS.register( BarElement, CategoryScale, LinearScale, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, RadialLinearScale ); interface ChartData { labels: string[]; values: number[]; } interface Stats { [key: string]: ChartData; } const FILTER_LABELS: Record = { status: "Status", organization: "Organization", country: "Country", legalBasis: "Legal Basis", }; interface DashboardProps { stats: Stats; filters: FilterState; setFilters: React.Dispatch>; availableFilters: AvailableFilters; } const chartTypes = ["bar","pie","doughnut","line","radar","polarArea"] as const; type ChartType = typeof chartTypes[number]; const Dashboard: React.FC = ({ stats: initialStats, filters, setFilters, availableFilters, }) => { const [orgInput, setOrgInput] = useState(""); const [statsData, setStatsData] = useState(initialStats); const [loadingStats, setLoadingStats] = useState(false); const fetchTimer = useRef(null); // Debounced stats & filters fetch useEffect(() => { if (fetchTimer.current) clearTimeout(fetchTimer.current); fetchTimer.current = window.setTimeout(() => { const qs = new URLSearchParams(); Object.entries(filters).forEach(([k, v]) => v && qs.set(k, v)); setLoadingStats(true); fetch(`/api/stats?${qs.toString()}`) .then(r => r.json()) .then((data: Stats) => setStatsData(data)) .catch(console.error) .finally(() => setLoadingStats(false)); }, 300); return () => { if (fetchTimer.current) clearTimeout(fetchTimer.current); }; }, [filters]); const updateFilter = (key: keyof FilterState) => (opt: { value: string } | null) => setFilters(prev => ({ ...prev, [key]: opt?.value || "" })); const updateSlider = ( k1: 'minYear' | 'minFunding', k2: 'maxYear' | 'maxFunding' ) => ([min, max]: number[]) => setFilters(prev => ({ ...prev, [k1]: String(min), [k2]: String(max) })); const filterKeys: Array = [ 'status', 'organization', 'country', 'legalBasis' ]; if (loadingStats && !Object.keys(statsData).length) { return ; } return ( {/* Filters */} {filterKeys.map(key => { const opts = availableFilters[ key === 'status' ? 'statuses' : key === 'organization' ? 'organizations' : key === 'country' ? 'countries' : 'legalBases' ] || []; const isOrg = key === 'organization'; return ( {FILTER_LABELS[key]}