import React, { useEffect, useState } from "react"; import { Box, Button, Flex, Heading, Input, Select as ChakraSelect, Spinner, Table, Tbody, Td, Th, Thead, Tr, VStack, HStack, Text, Avatar, } from "@chakra-ui/react"; import type { ProjectExplorerProps, Project, ChatMessage } from "../hooks/types"; interface FilterOptions { statuses: string[]; legalBases: string[]; organizations: string[]; countries: string[]; fundingSchemes: string[]; ids: string[]; topics: string[]; } const MIN_SEARCH_LEN = 3; type SortField = keyof Pick; type SortOrder = 'asc' | 'desc'; const ProjectExplorer: React.FC = ({ projects, search, setSearch, statusFilter, setStatusFilter, legalFilter, setLegalFilter, orgFilter, setOrgFilter, countryFilter, setCountryFilter, fundingSchemeFilter, setFundingSchemeFilter, idFilter, setIdFilter, topicsFilter, setTopicsFilter, setSortField, sortField, setSortOrder, sortOrder, page, setPage, setSelectedProject, question, setQuestion, chatHistory, askChatbot, loading, messagesEndRef, }) => { const [filterOpts, setFilterOpts] = useState({ statuses: [], legalBases: [], organizations: [], countries: [], fundingSchemes: [], ids: [], topics: [], }); const [loadingFilters, setLoadingFilters] = useState(false); // Fetch dynamic filter options whenever any filter changes useEffect(() => { setLoadingFilters(true); const params = new URLSearchParams(); if (statusFilter) params.set("status", statusFilter); if (legalFilter) params.set("legalBasis", legalFilter); if (orgFilter) params.set("organization", orgFilter); if (countryFilter) params.set("country", countryFilter); if (search.length >= MIN_SEARCH_LEN) params.set("search", search); if (idFilter.length >= MIN_SEARCH_LEN) params.set("proj_id", idFilter); if (fundingSchemeFilter) params.set("fundingScheme", fundingSchemeFilter); if (topicsFilter) params.set("topics", topicsFilter); params.set("sortField", sortField); params.set("sortOrder", sortOrder); fetch(`/api/filters?${params.toString()}`) .then((res) => res.json()) .then((data: FilterOptions) => setFilterOpts(data)) .catch(console.error) .finally(() => setLoadingFilters(false)); }, [ statusFilter, legalFilter, orgFilter, countryFilter, search, idFilter, fundingSchemeFilter, topicsFilter, sortField, sortOrder, ]); const fmtNum = (num: number | null | undefined): string => num != null ? num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '-'; const handleSort = (field: SortField) => { if (sortField === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortOrder('asc'); } setPage(0); }; const handleMinInput = (value: string, setter: (v: string) => void) => { setter(value); setPage(0); }; return ( {/* Left Pane: Projects & Filters */} { setSearch(e.target.value); setPage(0); }} width={{ base: "100%", md: "200px" }} /> handleMinInput(e.target.value, setIdFilter)} w="160px" isDisabled={loadingFilters} /> { setStatusFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="120px" > {filterOpts.statuses.map((s) => )} { setLegalFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="150px" > {filterOpts.legalBases.map((lb) => )} { setOrgFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="150px" > {filterOpts.organizations.map((o) => )} { setCountryFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="120px" > {filterOpts.countries.map((c) => )} { setFundingSchemeFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="180px" > {filterOpts.fundingSchemes.map((c) => )} { setTopicsFilter(e.target.value); setPage(0); }} isDisabled={loadingFilters} width="180px" > {filterOpts.topics.map((c) => )} {!projects.length ? ( ) : ( {projects.map((p: Project) => ( setSelectedProject(p)} cursor="pointer" _hover={{ bg: "gray.100" }} > ))}
handleSort('title')} cursor="pointer"> Title{sortField==='title'? (sortOrder==='asc'?' ↑':' ↓'):''} handleSort('status')} cursor="pointer"> Status{sortField==='status'? (sortOrder==='asc'?' ↑':' ↓'):''} handleSort('id')} cursor="pointer"> ID{sortField==='id'? (sortOrder==='asc'?' ↑':' ↓'):''} handleSort('startDate')} cursor="pointer"> Start Date{sortField==='startDate'? (sortOrder==='asc'?' ↑':' ↓'):''} handleSort('fundingScheme')} cursor="pointer"> Funding Scheme{sortField==='fundingScheme'? (sortOrder==='asc'?' ↑':' ↓'):''} handleSort('ecMaxContribution')} cursor="pointer"> Funding (€){sortField==='ecMaxContribution'? (sortOrder==='asc'?' ↑':' ↓'):''}
{p.title} {p.status} {p.id} {p.startDate} {p.fundingScheme || '-'} €{fmtNum(p.ecMaxContribution)}
)}
{/* Right Pane: Assistant */} Assistant ⚠️ The model may occasionally produce incorrect or misleading answers. {chatHistory.map((msg, i) => ( {msg.role === "assistant" && } {msg.content} {msg.content === "Generating answer..." && ( )} {msg.role === "user" && ( )} ))}
setQuestion(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); askChatbot(); } }} isDisabled={loading} size="md" // ← make the input a touch taller /> ); }; export default ProjectExplorer;