Rom89823974978 commited on
Commit
485ac5f
·
1 Parent(s): 1a1c017
frontend/src/components/ProjectExplorer.tsx CHANGED
@@ -29,6 +29,12 @@ interface FilterOptions {
29
  fundingSchemes: string[];
30
  ids: string[];
31
  }
 
 
 
 
 
 
32
 
33
  const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
34
  projects,
@@ -46,6 +52,10 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
46
  setFundingSchemeFilter,
47
  idFilter,
48
  setIdFilter,
 
 
 
 
49
  page,
50
  setPage,
51
  setSelectedProject,
@@ -74,8 +84,10 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
74
  if (orgFilter) params.set("organization", orgFilter);
75
  if (countryFilter) params.set("country", countryFilter);
76
  if (search) params.set("search", search);
77
- if (idFilter) params.set("id", idFilter);
78
  if (fundingSchemeFilter) params.set("fundingScheme", fundingSchemeFilter);
 
 
79
 
80
  fetch(`/api/filters?${params.toString()}`)
81
  .then((res) => res.json())
@@ -88,6 +100,22 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
88
  num != null
89
  ? num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
90
  : '-';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
 
93
  return (
@@ -101,15 +129,13 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
101
  onChange={(e) => { setSearch(e.target.value); setPage(0); }}
102
  width={{ base: "100%", md: "200px" }}
103
  />
104
- <ChakraSelect
105
- placeholder={loadingFilters ? "Loading..." : "ID"}
106
  value={idFilter}
107
- onChange={(e) => { setIdFilter(e.target.value); setPage(0); }}
 
108
  isDisabled={loadingFilters}
109
- width="100px"
110
- >
111
- {filterOpts.fundingSchemes.map((c) => <option key={c} value={c}>{c}</option>)}
112
- </ChakraSelect>
113
  <ChakraSelect
114
  placeholder={loadingFilters ? "Loading..." : "Status"}
115
  value={statusFilter}
@@ -176,12 +202,28 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
176
  >
177
  <Thead>
178
  <Tr>
179
- <Th width="50%" whiteSpace="nowrap">Title</Th>
180
- <Th width="10%" whiteSpace="nowrap">Status</Th>
181
- <Th width="10%" whiteSpace="nowrap">ID</Th>
182
- <Th width="10%" whiteSpace="nowrap">Start Date</Th>
183
- <Th width="10%" whiteSpace="nowrap">Funding Scheme</Th>
184
- <Th width="10%" whiteSpace="nowrap">Funding</Th>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  </Tr>
186
  </Thead>
187
  <Tbody>
 
29
  fundingSchemes: string[];
30
  ids: string[];
31
  }
32
+ const MIN_SEARCH_LEN = 5;
33
+
34
+ type SortField = keyof Pick<Project, 'title' | 'status' | 'id' | 'startDate' | 'fundingScheme' | 'ecMaxContribution'>;
35
+
36
+ type SortOrder = 'asc' | 'desc';
37
+
38
 
39
  const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
40
  projects,
 
52
  setFundingSchemeFilter,
53
  idFilter,
54
  setIdFilter,
55
+ setSortField,
56
+ sortField,
57
+ setSortOrder,
58
+ sortOrder,
59
  page,
60
  setPage,
61
  setSelectedProject,
 
84
  if (orgFilter) params.set("organization", orgFilter);
85
  if (countryFilter) params.set("country", countryFilter);
86
  if (search) params.set("search", search);
87
+ if (idFilter.length >= MIN_SEARCH_LEN) params.set("id", idFilter);
88
  if (fundingSchemeFilter) params.set("fundingScheme", fundingSchemeFilter);
89
+ params.set("sortField", sortField);
90
+ params.set("sortOrder", sortOrder);
91
 
92
  fetch(`/api/filters?${params.toString()}`)
93
  .then((res) => res.json())
 
100
  num != null
101
  ? num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
102
  : '-';
103
+
104
+ const handleSort = (field: SortField) => {
105
+ if (sortField === field) {
106
+ // toggle using current sortOrder value
107
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
108
+ } else {
109
+ setSortField(field);
110
+ setSortOrder('asc');
111
+ }
112
+ setPage(0);
113
+ };
114
+
115
+ const handleMinInput = (value: string, setter: (v: string) => void) => {
116
+ setter(value);
117
+ setPage(0);
118
+ };
119
 
120
 
121
  return (
 
129
  onChange={(e) => { setSearch(e.target.value); setPage(0); }}
130
  width={{ base: "100%", md: "200px" }}
131
  />
132
+ <Input
133
+ placeholder={`ID (min ${MIN_SEARCH_LEN})`}
134
  value={idFilter}
135
+ onChange={(e) => handleMinInput(e.target.value, setIdFilter)}
136
+ w="100px"
137
  isDisabled={loadingFilters}
138
+ />
 
 
 
139
  <ChakraSelect
140
  placeholder={loadingFilters ? "Loading..." : "Status"}
141
  value={statusFilter}
 
202
  >
203
  <Thead>
204
  <Tr>
205
+ <Thead>
206
+ <Tr>
207
+ <Th w="50%" whiteSpace="nowrap" onClick={() => handleSort('title')} cursor="pointer">
208
+ Title{sortField==='title'? (sortOrder==='asc'?' ↑':' ↓'):''}
209
+ </Th>
210
+ <Th w="10%" whiteSpace="nowrap" onClick={() => handleSort('status')} cursor="pointer">
211
+ Status{sortField==='status'? (sortOrder==='asc'?' ↑':' ↓'):''}
212
+ </Th>
213
+ <Th w="10%" whiteSpace="nowrap" onClick={() => handleSort('id')} cursor="pointer">
214
+ ID{sortField==='id'? (sortOrder==='asc'?' ↑':' ↓'):''}
215
+ </Th>
216
+ <Th w="10%" whiteSpace="nowrap" onClick={() => handleSort('startDate')} cursor="pointer">
217
+ Start Date{sortField==='startDate'? (sortOrder==='asc'?' ↑':' ↓'):''}
218
+ </Th>
219
+ <Th w="10%" whiteSpace="nowrap" onClick={() => handleSort('fundingScheme')} cursor="pointer">
220
+ Funding Scheme{sortField==='fundingScheme'? (sortOrder==='asc'?' ↑':' ↓'):''}
221
+ </Th>
222
+ <Th w="10%" whiteSpace="nowrap" onClick={() => handleSort('ecMaxContribution')} cursor="pointer">
223
+ Funding (€){sortField==='ecMaxContribution'? (sortOrder==='asc'?' ↑':' ↓'):''}
224
+ </Th>
225
+ </Tr>
226
+ </Thead>
227
  </Tr>
228
  </Thead>
229
  <Tbody>
frontend/src/hooks/types.ts CHANGED
@@ -93,6 +93,10 @@ export interface ProjectExplorerProps {
93
  setFundingSchemeFilter: (value: string) => void;
94
  idFilter: string;
95
  setIdFilter: (value: string) => void;
 
 
 
 
96
  page: number;
97
  setPage: React.Dispatch<React.SetStateAction<number>>;
98
  setSelectedProject: (project: Project) => void;
 
93
  setFundingSchemeFilter: (value: string) => void;
94
  idFilter: string;
95
  setIdFilter: (value: string) => void;
96
+ setSortField: (field: string) => void;
97
+ sortField: string;
98
+ setSortOrder : (order: "asc" | "desc") => void;
99
+ sortOrder : "asc" | "desc";
100
  page: number;
101
  setPage: React.Dispatch<React.SetStateAction<number>>;
102
  setSelectedProject: (project: Project) => void;
frontend/src/hooks/useAppState.ts CHANGED
@@ -22,6 +22,8 @@ export const useAppState = () => {
22
  const [countryFilter, setCountryFilter] = useState('');
23
  const [fundingSchemeFilter, setFundingSchemeFilter ] = useState('');
24
  const [idFilter, setIdFilter] = useState('');
 
 
25
  const [filters, setFilters] = useState<FilterState>({
26
  status: "",
27
  organization: "",
@@ -45,7 +47,7 @@ export const useAppState = () => {
45
  const messagesEndRef = useRef<HTMLDivElement | null>(null);
46
 
47
  const fetchProjects = () => {
48
- fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}&legalBasis=${legalFilter}&organization=${orgFilter}&country=${countryFilter}&fundingScheme=${fundingSchemeFilter}&id=${idFilter}`)
49
  .then(res => res.json())
50
  .then((data: Project[]) => setProjects(data))
51
  .catch(console.error);
@@ -95,7 +97,7 @@ export const useAppState = () => {
95
  }
96
  };
97
 
98
- useEffect(fetchProjects, [page, search, statusFilter,legalFilter, orgFilter, countryFilter, fundingSchemeFilter, idFilter]);
99
  useEffect(() => {
100
  console.log("Updated filters:", filters);
101
  fetchStats(filters);
@@ -126,6 +128,10 @@ export const useAppState = () => {
126
  setFundingSchemeFilter,
127
  idFilter,
128
  setIdFilter,
 
 
 
 
129
  page,
130
  setPage,
131
  setSelectedProject,
 
22
  const [countryFilter, setCountryFilter] = useState('');
23
  const [fundingSchemeFilter, setFundingSchemeFilter ] = useState('');
24
  const [idFilter, setIdFilter] = useState('');
25
+ const [sortField, setSortField] = useState('');
26
+ const [sortOrder, setSortOrder] = useState('');
27
  const [filters, setFilters] = useState<FilterState>({
28
  status: "",
29
  organization: "",
 
47
  const messagesEndRef = useRef<HTMLDivElement | null>(null);
48
 
49
  const fetchProjects = () => {
50
+ fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}&legalBasis=${legalFilter}&organization=${orgFilter}&country=${countryFilter}&fundingScheme=${fundingSchemeFilter}&id=${idFilter}&sortField=${sortField}&sortOrder=${sortOrder}`)
51
  .then(res => res.json())
52
  .then((data: Project[]) => setProjects(data))
53
  .catch(console.error);
 
97
  }
98
  };
99
 
100
+ useEffect(fetchProjects, [page, search, statusFilter,legalFilter, orgFilter, countryFilter, fundingSchemeFilter, idFilter, sortField, sortOrder]);
101
  useEffect(() => {
102
  console.log("Updated filters:", filters);
103
  fetchStats(filters);
 
128
  setFundingSchemeFilter,
129
  idFilter,
130
  setIdFilter,
131
+ setSortField,
132
+ sortField,
133
+ setSortOrder,
134
+ sortOrder,
135
  page,
136
  setPage,
137
  setSelectedProject,