import { useState, useEffect, useRef } from "react";//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); // ref to hold our debounce timer const fetchTimer = useRef(null); useEffect(() => { // clear any pending fetch if (fetchTimer.current) { clearTimeout(fetchTimer.current); } // schedule new fetch 300ms after last filter change fetchTimer.current = window.setTimeout(() => { const qs = new URLSearchParams(); if (filters.status) qs.set("status", filters.status); if (filters.organization) qs.set("organization", filters.organization); if (filters.country) qs.set("country", filters.country); if (filters.legalBasis) qs.set("legalBasis", filters.legalBasis); qs.set("minYear", filters.minYear); qs.set("maxYear", filters.maxYear); qs.set("minFunding", filters.minFunding); qs.set("maxFunding", filters.maxFunding); setLoadingStats(true); fetch(`/api/stats?${qs.toString()}`) .then(res => res.json()) .then((data: Stats) => setStatsData(data)) .catch(console.error) .finally(() => setLoadingStats(false)); }, 300); // cleanup on unmount or next effect-run return () => { if (fetchTimer.current) { clearTimeout(fetchTimer.current); } }; }, [ filters.status, filters.organization, filters.country, filters.legalBasis, filters.minYear, filters.maxYear, filters.minFunding, filters.maxFunding, ]); 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", ]; // initial blank-state spinner if (loadingStats && !Object.keys(statsData).length) { return ( ); } return ( {/* Filters */} {filterKeys.map((key) => { const isOrg = key === "organization"; const opts = availableFilters[ key === "status" ? "statuses" : key === "organization" ? "organizations" : key === "country" ? "countries" : "legalBases" ] || []; return ( {FILTER_LABELS[key]}