import React, { useState, useEffect, useRef } from "react"; import { debounce } from "lodash"; import Select from "react-select"; import { ChakraProvider, Tabs, TabList, TabPanels, Tab, TabPanel, Heading, Flex, Box, Table, Thead, Tbody, Tr, Th, Td, Input, Select as ChakraSelect, Button, extendTheme, SimpleGrid, VStack, HStack, Text, Avatar, Spacer } from "@chakra-ui/react"; import { Bar } from "react-chartjs-2"; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, } from "chart.js"; import type { ChartOptions } from "chart.js"; ChartJS.register( CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend ); const theme = extendTheme({ colors: { brand: { blue: "#003399", yellow: "#FFCC00" } } }); const Header = () => ( EU Project Explorer ); interface Project { startDate: string; id: string; title: string; status: string; ecMaxContribution: number; } function App() { const [question, setQuestion] = useState(""); const [chatHistory, setChatHistory] = useState<{ role: string, content: string }[]>([]); const [projects, setProjects] = useState([]); const [stats, setStats] = useState<{ [key: string]: any }>({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const [statusFilter, setStatusFilter] = useState(""); const [selectedProject, setSelectedProject] = useState(null); const messagesEndRef = useRef(null); const [dashboardStatusFilter, setDashboardStatusFilter] = useState(""); const [dashboardOrgFilter, setDashboardOrgFilter] = useState(""); const [dashboardCountryFilter, setDashboardCountryFilter] = useState(""); const [dashboardLegalBasisFilter, setDashboardLegalBasisFilter] = useState(""); const [availableStatuses, setAvailableStatuses] = useState(["SIGNED", "CLOSED", "TERMINATED"]); const [availableOrgs, setAvailableOrgs] = useState([]); const [availableCountries, setAvailableCountries] = useState([]); const [availableLegalBases, setAvailableLegalBases] = useState([]); useEffect(() => { const fetchFilters = (status: string, org: string, country: string, legalBasis: string) => { const params = new URLSearchParams({ status, organization: org, country, legalBasis }); fetch(`/api/filters?${params.toString()}`) .then(res => res.json()) .then(data => { setAvailableOrgs(data.organizations); setAvailableCountries(data.countries); setAvailableLegalBases(data.legalBases); }); }; fetchFilters(dashboardStatusFilter, dashboardOrgFilter, dashboardCountryFilter, dashboardLegalBasisFilter); }, [dashboardStatusFilter, dashboardOrgFilter, dashboardCountryFilter, dashboardLegalBasisFilter]); const debouncedFetchStats = useRef( debounce((status, org, country, legalBasis) => { fetch(`/api/stats?status=${status}&organization=${org}&country=${country}&legalBasis=${legalBasis}`) .then(res => res.json()) .then(setStats) .catch(console.error); }, 500) ).current; useEffect(() => { debouncedFetchStats(dashboardStatusFilter, dashboardOrgFilter, dashboardCountryFilter, dashboardLegalBasisFilter); }, [dashboardStatusFilter, dashboardOrgFilter, dashboardCountryFilter, dashboardLegalBasisFilter]); useEffect(() => { fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}`) .then(res => { if (!res.ok) throw new Error("Failed to fetch projects"); return res.json(); }) .then(data => setProjects(data)) .catch(err => console.error("Error fetching projects:", err)); }, [page, search, statusFilter]); useEffect(() => { fetch(`/api/stats?status=${dashboardStatusFilter}&organization=${dashboardOrgFilter}&country=${dashboardCountryFilter}&legalBasis=${dashboardLegalBasisFilter}`) .then(res => res.json()) .then(setStats) .catch(console.error); }, []); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [chatHistory]); const askChatbot = async () => { if (!question.trim()) return; const newChat = [...chatHistory, { role: "user", content: question }]; setChatHistory(newChat); setQuestion(""); try { const res = await fetch("/api/chat/query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question }) }); const data = await res.json(); setChatHistory([...newChat, { role: "assistant", content: data.answer }]); } catch { setChatHistory([...newChat, { role: "assistant", content: "Something went wrong." }]); } }; const buildChart = (label: string, labels: string[], values: number[]) => ({ data: { labels, datasets: [ { label, data: values, backgroundColor: "#FFCC00", borderColor: "#003399", borderWidth: 1, } ] }, options: { responsive: true, plugins: { legend: { position: "top" }, title: { display: true, text: label }, }, } as ChartOptions<'bar'> }); return (
Dashboard Projects + Chat {selectedProject && Project Details} Funding Overview ({ label: o, value: o }))} placeholder="Organization" onChange={(e) => setDashboardOrgFilter(e?.value || "")} onKeyDown={(e) => { if (e.key === "Enter") { const params = new URLSearchParams({ status: dashboardStatusFilter, organization: dashboardOrgFilter, country: dashboardCountryFilter, legalBasis: dashboardLegalBasisFilter }); fetch(`/api/filters?${params.toString()}`) .then(res => res.json()) .then(data => { setAvailableOrgs(data.organizations); setAvailableCountries(data.countries); setAvailableLegalBases(data.legalBases); }); } }} isClearable/> ({ label: o, value: o }))} placeholder="Legal Basis" onChange={(e) => setDashboardLegalBasisFilter(e?.value || "")} onKeyDown={(e) => { if (e.key === "Enter") { const params = new URLSearchParams({ status: dashboardStatusFilter, organization: dashboardOrgFilter, country: dashboardCountryFilter, legalBasis: dashboardLegalBasisFilter }); fetch(`/api/filters?${params.toString()}`) .then(res => res.json()) .then(data => { setAvailableOrgs(data.organizations); setAvailableCountries(data.countries); setAvailableLegalBases(data.legalBases); }); } }} isClearable/> {dashboardStatusFilter && Status: {dashboardStatusFilter}} {dashboardOrgFilter && Org: {dashboardOrgFilter}} {dashboardCountryFilter && Country: {dashboardCountryFilter}} {dashboardLegalBasisFilter && Legal Basis: {dashboardLegalBasisFilter}} {Object.keys(stats).map((key, i) => { const chart = buildChart(key, stats[key].labels, stats[key].values); return ( ); })} Projects { setSearch(e.target.value); setPage(0); }} /> { setStatusFilter(e.target.value); setPage(0); }} > {projects.map((p, i) => ( setSelectedProject(p)} cursor="pointer"> ))}
Title Status ID Start Date Funding €
{p.title} {p.status} {p.id} {p.startDate} {p.ecMaxContribution?.toLocaleString()}
Assistant {chatHistory.map((msg, i) => ( {msg.role === "assistant" && } {msg.content} {msg.role === "user" && } ))}
setQuestion(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); askChatbot(); } }} /> {selectedProject && ( {selectedProject.title}

ID: {selectedProject.id}

Status: {selectedProject.status}

Start Date: {selectedProject.startDate}

Funding: €{selectedProject.ecMaxContribution.toLocaleString()}

)} ); } export default App;