Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit 13700cb

Browse files
authored
Merge pull request #152 from DigitalProductInnovationAndDevelopment/TopicTrends&AutoComplete&SearchApiUpdate
Topic trends&auto complete&search api update
2 parents c3dfe58 + 6898d69 commit 13700cb

17 files changed

Lines changed: 1232 additions & 514 deletions

backend/routes/autocomplete.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,29 @@ router.get('/', async (req, res) => {
8080
}
8181
}
8282

83-
if (type === 'institution' && query.length >= 2) {
83+
if (type === 'institution' && query.length >= 1) {
8484
try {
85-
const url = `${OPENALEX_API_BASE}/institutions`;
86-
const params = { search: query, per_page: 10 };
85+
// Use OpenAlex autocomplete endpoint for better partial matching
86+
const url = `${OPENALEX_API_BASE}/autocomplete`;
87+
const params = {
88+
q: query,
89+
mailto: 'team@ourresearch.org'
90+
};
8791
const response = await axios.get(url, { params, headers: OPENALEX_HEADERS });
8892
const data = response.data;
89-
const results = (data.results || []).map(inst => ({
90-
id: inst.id,
91-
display_name: inst.display_name
92-
}));
93-
return res.json({ results });
93+
94+
// Filter results to only include institutions
95+
const institutionResults = (data.results || [])
96+
.filter(item => item.entity_type === 'institution')
97+
.map(inst => ({
98+
id: inst.id,
99+
display_name: inst.display_name
100+
}));
101+
102+
return res.json({ results: institutionResults });
94103
} catch (err) {
95-
return res.status(500).json({ results: [], error: 'Failed to fetch from OpenAlex' });
104+
console.error('OpenAlex autocomplete error:', err.message);
105+
return res.status(500).json({ results: [], error: 'Failed to fetch from OpenAlex autocomplete' });
96106
}
97107
}
98108

backend/routes/publications.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ router.get('/keyword_trends', async (req, res) => {
146146
start_date,
147147
end_date,
148148
per_page = 200,
149-
years,
150-
limit
149+
institution_id
151150
} = req.query;
152151

153152
let filterParts = [];
@@ -159,6 +158,11 @@ router.get('/keyword_trends', async (req, res) => {
159158
if (keyword) {
160159
filterParts.push(`title_and_abstract.search:${keyword}`);
161160
}
161+
// Add institution filter if provided
162+
if (institution_id) {
163+
filterParts.push(`authorships.institutions.id:I${institution_id}`);
164+
}
165+
162166
// Only add date filters if start_date or end_date is provided
163167
if (start_date) {
164168
filterParts.push(`from_publication_date:${start_date}`);
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,53 @@
11
.dropdownContainer {
22
position: relative;
33
width: 100%;
4-
max-width: 400px;
5-
margin-bottom: 1rem;
4+
margin-bottom: 0;
65
}
76
.label {
87
font-weight: 600;
98
margin-bottom: 0.25rem;
109
display: block;
1110
}
1211
.inputWrapper {
12+
position: relative;
1313
display: flex;
1414
align-items: center;
1515
}
1616
.input {
1717
width: 100%;
18-
padding: 0.5rem;
19-
border: 1px solid #ccc;
20-
border-radius: 4px 0 0 4px;
18+
padding: 0.75rem;
19+
border: 1px solid #e5e7eb;
20+
border-radius: 6px;
2121
outline: none;
22-
font-size: 1rem;
22+
font-size: 0.875rem;
23+
background-color: #fff;
24+
transition: border-color 0.2s ease;
25+
height: 48px;
26+
box-sizing: border-box;
27+
line-height: 1.5;
28+
}
29+
30+
.input:focus {
31+
border-color: #4F6AF6;
32+
box-shadow: 0 0 0 2px rgba(79, 106, 246, 0.1);
2333
}
2434
.toggleBtn {
25-
border: 1px solid #ccc;
26-
border-left: none;
27-
background: #f7f7f7;
28-
padding: 0.5rem 0.75rem;
29-
border-radius: 0 4px 4px 0;
35+
position: absolute;
36+
right: 32px;
37+
top: 50%;
38+
transform: translateY(-50%);
39+
border: none;
40+
background: transparent;
41+
padding: 0.25rem;
3042
cursor: pointer;
31-
font-size: 1rem;
43+
font-size: 0.75rem;
44+
color: #6b7280;
45+
z-index: 5;
46+
height: 24px;
47+
width: 24px;
48+
display: flex;
49+
align-items: center;
50+
justify-content: center;
3251
}
3352
.dropdownList {
3453
position: absolute;
@@ -38,26 +57,83 @@
3857
max-height: 220px;
3958
overflow-y: auto;
4059
background: #fff;
41-
border: 1px solid #ccc;
42-
border-top: none;
60+
border: 1px solid #e5e7eb;
61+
border-radius: 6px;
4362
z-index: 10;
44-
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
63+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
64+
margin-top: 4px;
4565
}
4666
.dropdownItem {
47-
padding: 0.5rem 1rem;
67+
padding: 0.75rem 1rem;
4868
cursor: pointer;
4969
transition: background 0.15s;
70+
border-bottom: 1px solid #f3f4f6;
71+
}
72+
73+
.dropdownItem:last-child {
74+
border-bottom: none;
5075
}
76+
5177
.dropdownItem:hover {
52-
background: #f0f0f0;
78+
background: #f9fafb;
5379
}
5480
.loading, .noResults {
5581
padding: 0.75rem 1rem;
56-
color: #888;
82+
color: #6b7280;
5783
text-align: center;
84+
font-size: 0.875rem;
5885
}
5986
.selectedValue {
6087
margin-top: 0.5rem;
61-
font-size: 0.95rem;
62-
color: #333;
88+
font-size: 0.875rem;
89+
color: #4F6AF6;
90+
font-weight: 500;
91+
}
92+
93+
/* Dark mode styles */
94+
:global(.dark) .label {
95+
color: #ccc;
96+
}
97+
98+
:global(.dark) .input {
99+
background-color: #1a1a1a;
100+
border: 1px solid #404040;
101+
color: #fff;
102+
}
103+
104+
:global(.dark) .input:focus {
105+
border-color: #4F6AF6;
106+
box-shadow: 0 0 0 2px rgba(79, 106, 246, 0.2);
107+
}
108+
109+
:global(.dark) .toggleBtn {
110+
color: #ccc;
111+
}
112+
113+
:global(.dark) .toggleBtn:hover {
114+
color: #4F6AF6;
115+
}
116+
117+
:global(.dark) .dropdownList {
118+
background: #2a2a2a;
119+
border: 1px solid #404040;
120+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
121+
}
122+
123+
:global(.dark) .dropdownItem {
124+
border-bottom: 1px solid #404040;
125+
color: #fff;
126+
}
127+
128+
:global(.dark) .dropdownItem:hover {
129+
background: #404040;
130+
}
131+
132+
:global(.dark) .loading,
133+
:global(.dark) .noResults {
134+
color: #ccc;
135+
}
136+
137+
:global(.dark) .selectedValue {
138+
color: #4F6AF6;
63139
}

0 commit comments

Comments
 (0)