MDA / frontend /src /hooks /useAppState.ts
Rom89823974978's picture
Connected funding schemes too
5bc74c0
import { useState, useEffect, useRef } from "react";
import { debounce } from "lodash";
import type { Project, OrganizationLocation, FilterState, ChatMessage,AvailableFilters } from "./types";
interface Stats {
[key: string]: {
labels: string[];
values: number[];
};
}
type SortOrder = "asc" | "desc";
export const useAppState = () => {
const [projects, setProjects] = useState<Project[]>([]);
const [search, setSearch] = useState<string>("");
const [statusFilter, setStatusFilter] = useState<string>("");
const [page, setPage] = useState<number>(0);
const [question, setQuestion] = useState<string>("");
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const [stats, setStats] = useState<Stats>({});
const [legalFilter, setLegalFilter] = useState('');
const [orgFilter, setOrgFilter] = useState('');
const [countryFilter, setCountryFilter] = useState('');
const [fundingSchemeFilter, setFundingSchemeFilter ] = useState('');
const [idFilter, setIdFilter] = useState('');
const [topicsFilter, setTopicsFilter] = useState('');
const [sortField, setSortField] = useState('');
const [sortOrder, setSortOrder] = useState<SortOrder>("asc");
const [filters, setFilters] = useState<FilterState>({
status: "",
organization: "",
country: "",
legalBasis: "",
topic: "",
fundingScheme: "",
minYear: "2000",
maxYear: "2025",
minEndYear: "2000",
maxEndYear: "2035",
minFunding: "0",
maxFunding: "10000000",
});
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [availableFilters, setAvailableFilters] = useState<AvailableFilters>({
statuses: ["SIGNED", "CLOSED", "TERMINATED","UNKNOWN"],
organizations: [],
countries: [],
legalBases: [],
fundingSchemes:[],
ids:[],
topics: []
});
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const fetchProjects = () => {
fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}&legalBasis=${legalFilter}&organization=${orgFilter}&country=${countryFilter}&fundingScheme=${fundingSchemeFilter}&proj_id=${idFilter}&topic=${topicsFilter}&sortField=${sortField}&sortOrder=${sortOrder}`)
.then(res => res.json())
.then((data: Project[]) => setProjects(data))
.catch(console.error);
};
const fetchStats = debounce((filters: FilterState) => {
const params = new URLSearchParams(filters);
fetch(`/api/stats?${params.toString()}`)
.then(res => res.json())
.then((data: Stats) => setStats(data))
.catch(console.error);
}, 500);
const fetchAvailableFilters = (filters: FilterState) => {
const params = new URLSearchParams(filters);
fetch(`/api/filters?${params.toString()}`)
.then(res => res.json())
.then((data: Omit<AvailableFilters, 'statuses'>) => {
setAvailableFilters({
statuses: ["SIGNED", "CLOSED", "TERMINATED", "UNKNOWN"],
organizations: data.organizations,
countries: data.countries,
legalBases: data.legalBases,
fundingSchemes: data.fundingSchemes,
ids: [],
topics: data.topics
});
});
};
interface RagResponse {
answer: string;
source_ids: string[];
}
const askChatbot = async () => {
if (!question.trim() || loading) return;
const newChat: ChatMessage[] = [
...chatHistory,
{ role: "user", content: question },
];
setChatHistory(newChat);
setQuestion("");
setLoading(true);
// 1) placeholder
setChatHistory((h) => [
...h,
{ role: "assistant", content: "Generating answer..." },
]);
try {
const res = await fetch("/api/rag", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: question }),
});
const text = await res.text();
if (!res.ok) {
let errDetail = text;
try {
errDetail = JSON.parse(text).detail;
} catch {}
throw new Error(errDetail);
}
const data: RagResponse = JSON.parse(text);
const idList = data.source_ids.join(", ") || "none";
const assistantContent = `${data.answer}
The output was based on the following Project IDs: ${idList}`;
// 2) replace placeholder with real answer
setChatHistory((h) => [
...h.slice(0, -1),
{ role: "assistant", content: assistantContent },
]);
} catch (err: any) {
// replace placeholder with error message
setChatHistory((h) => [
...h.slice(0, -1),
{
role: "assistant",
content: `Something went wrong: ${err.message}`,
},
]);
} finally {
setLoading(false);
// scroll to bottom
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}
};
useEffect(() => {
// If the user has typed something but it's too short, don't refetch
fetchProjects();
}, [
page,
search,
statusFilter,
legalFilter,
orgFilter,
countryFilter,
fundingSchemeFilter,
idFilter,
sortField,
sortOrder,
]);
useEffect(() => {
console.log("Updated filters:", filters);
fetchStats(filters);
}, [filters]);
useEffect(() => fetchAvailableFilters(filters), [filters]);
return {
selectedProject,
dashboardProps: {
stats,
filters,
setFilters,
availableFilters
},
explorerProps: {
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,
setChatHistory,
askChatbot,
loading,
messagesEndRef
},
detailsProps: {
project: selectedProject!,
question,
setQuestion,
chatHistory,
askChatbot,
loading,
messagesEndRef
}
};
};