Spaces:
Sleeping
Sleeping
Commit
·
e259887
1
Parent(s):
932823b
frontend/src/components/Dashboard.tsx
CHANGED
@@ -72,33 +72,28 @@ const Dashboard: React.FC<DashboardProps> = ({
|
|
72 |
const [orgInput, setOrgInput] = useState("");
|
73 |
const [statsData, setStatsData] = useState<Stats>(initialStats);
|
74 |
const [loadingStats, setLoadingStats] = useState(false);
|
75 |
-
const fetchTimer = useRef<number | null>(null);
|
76 |
|
77 |
// Debounced stats & filters fetch
|
78 |
useEffect(() => {
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
.catch(console.error);
|
99 |
-
}, 300);
|
100 |
-
return () => { if (fetchTimer.current) clearTimeout(fetchTimer.current); };
|
101 |
-
}, [filters, setFilters]);
|
102 |
|
103 |
const updateFilter = (key: keyof FilterState) =>
|
104 |
(opt: { value: string } | null) =>
|
|
|
72 |
const [orgInput, setOrgInput] = useState("");
|
73 |
const [statsData, setStatsData] = useState<Stats>(initialStats);
|
74 |
const [loadingStats, setLoadingStats] = useState(false);
|
|
|
75 |
|
76 |
// Debounced stats & filters fetch
|
77 |
useEffect(() => {
|
78 |
+
const qs = new URLSearchParams();
|
79 |
+
Object.entries(filters).forEach(([key, val]) => {
|
80 |
+
if (val) qs.set(key, val);
|
81 |
+
});
|
82 |
+
|
83 |
+
setLoadingStats(true);
|
84 |
+
// Fetch stats
|
85 |
+
fetch(`/api/stats?${qs.toString()}`)
|
86 |
+
.then(res => res.json())
|
87 |
+
.then((data: Stats) => setStatsData(data))
|
88 |
+
.catch(console.error)
|
89 |
+
.finally(() => setLoadingStats(false));
|
90 |
+
|
91 |
+
// Fetch available filters
|
92 |
+
fetch(`/api/filters?${qs.toString()}`)
|
93 |
+
.then(res => res.json())
|
94 |
+
.then((data: AvailableFilters) => setFilters(prev => ({ ...prev, ...{} } as any)))
|
95 |
+
.catch(console.error);
|
96 |
+
}, [filters]);
|
|
|
|
|
|
|
|
|
97 |
|
98 |
const updateFilter = (key: keyof FilterState) =>
|
99 |
(opt: { value: string } | null) =>
|
frontend/src/components/ProjectExplorer.tsx
CHANGED
@@ -77,7 +77,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
77 |
return (
|
78 |
<Flex direction={{ base: "column", md: "row" }} gap={6}>
|
79 |
{/* Left Pane: Projects & Filters */}
|
80 |
-
<Box flex={{ base: "none", md: "0 0 600px" }} width={{ base: "100%", md: "
|
81 |
<Heading size="sm" mb={2}>Projects</Heading>
|
82 |
<Flex gap={4} mb={4} flexWrap="wrap">
|
83 |
<Input
|
@@ -91,7 +91,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
91 |
value={statusFilter}
|
92 |
onChange={(e) => { setStatusFilter(e.target.value); setPage(0); }}
|
93 |
isDisabled={loadingFilters}
|
94 |
-
width="
|
95 |
>
|
96 |
{filterOpts.statuses.map((s) => <option key={s} value={s}>{s}</option>)}
|
97 |
</ChakraSelect>
|
@@ -100,7 +100,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
100 |
value={legalFilter}
|
101 |
onChange={(e) => { setLegalFilter(e.target.value); setPage(0); }}
|
102 |
isDisabled={loadingFilters}
|
103 |
-
width="
|
104 |
>
|
105 |
{filterOpts.legalBases.map((lb) => <option key={lb} value={lb}>{lb}</option>)}
|
106 |
</ChakraSelect>
|
@@ -109,7 +109,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
109 |
value={orgFilter}
|
110 |
onChange={(e) => { setOrgFilter(e.target.value); setPage(0); }}
|
111 |
isDisabled={loadingFilters}
|
112 |
-
width="
|
113 |
>
|
114 |
{filterOpts.organizations.map((o) => <option key={o} value={o}>{o}</option>)}
|
115 |
</ChakraSelect>
|
@@ -118,7 +118,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
118 |
value={countryFilter}
|
119 |
onChange={(e) => { setCountryFilter(e.target.value); setPage(0); }}
|
120 |
isDisabled={loadingFilters}
|
121 |
-
width="
|
122 |
>
|
123 |
{filterOpts.countries.map((c) => <option key={c} value={c}>{c}</option>)}
|
124 |
</ChakraSelect>
|
|
|
77 |
return (
|
78 |
<Flex direction={{ base: "column", md: "row" }} gap={6}>
|
79 |
{/* Left Pane: Projects & Filters */}
|
80 |
+
<Box flex={{ base: "none", md: "0 0 600px" }} width={{ base: "100%", md: "900px" }}>
|
81 |
<Heading size="sm" mb={2}>Projects</Heading>
|
82 |
<Flex gap={4} mb={4} flexWrap="wrap">
|
83 |
<Input
|
|
|
91 |
value={statusFilter}
|
92 |
onChange={(e) => { setStatusFilter(e.target.value); setPage(0); }}
|
93 |
isDisabled={loadingFilters}
|
94 |
+
width="120px"
|
95 |
>
|
96 |
{filterOpts.statuses.map((s) => <option key={s} value={s}>{s}</option>)}
|
97 |
</ChakraSelect>
|
|
|
100 |
value={legalFilter}
|
101 |
onChange={(e) => { setLegalFilter(e.target.value); setPage(0); }}
|
102 |
isDisabled={loadingFilters}
|
103 |
+
width="120px"
|
104 |
>
|
105 |
{filterOpts.legalBases.map((lb) => <option key={lb} value={lb}>{lb}</option>)}
|
106 |
</ChakraSelect>
|
|
|
109 |
value={orgFilter}
|
110 |
onChange={(e) => { setOrgFilter(e.target.value); setPage(0); }}
|
111 |
isDisabled={loadingFilters}
|
112 |
+
width="150px"
|
113 |
>
|
114 |
{filterOpts.organizations.map((o) => <option key={o} value={o}>{o}</option>)}
|
115 |
</ChakraSelect>
|
|
|
118 |
value={countryFilter}
|
119 |
onChange={(e) => { setCountryFilter(e.target.value); setPage(0); }}
|
120 |
isDisabled={loadingFilters}
|
121 |
+
width="120px"
|
122 |
>
|
123 |
{filterOpts.countries.map((c) => <option key={c} value={c}>{c}</option>)}
|
124 |
</ChakraSelect>
|
frontend/src/hooks/useAppState.ts
CHANGED
@@ -49,13 +49,13 @@ export const useAppState = () => {
|
|
49 |
.catch(console.error);
|
50 |
};
|
51 |
|
52 |
-
const fetchStats =
|
53 |
const params = new URLSearchParams(filters);
|
54 |
fetch(`/api/stats?${params.toString()}`)
|
55 |
.then(res => res.json())
|
56 |
.then((data: Stats) => setStats(data))
|
57 |
.catch(console.error);
|
58 |
-
}
|
59 |
|
60 |
const fetchAvailableFilters = (filters: FilterState) => {
|
61 |
const params = new URLSearchParams(filters);
|
|
|
49 |
.catch(console.error);
|
50 |
};
|
51 |
|
52 |
+
const fetchStats = (filters: FilterState) => {
|
53 |
const params = new URLSearchParams(filters);
|
54 |
fetch(`/api/stats?${params.toString()}`)
|
55 |
.then(res => res.json())
|
56 |
.then((data: Stats) => setStats(data))
|
57 |
.catch(console.error);
|
58 |
+
};
|
59 |
|
60 |
const fetchAvailableFilters = (filters: FilterState) => {
|
61 |
const params = new URLSearchParams(filters);
|