Skip to content

Commit 2f2d11e

Browse files
Merge pull request #4867 from OneCommunityGlobal/Sayali_Add_Search_To_Community_Page
Sayali: add search functionality to community members page
2 parents d65a38b + 09050f1 commit 2f2d11e

3 files changed

Lines changed: 116 additions & 22 deletions

File tree

src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import PropTypes from 'prop-types';
12
import { useState } from 'react';
23
import { useSelector } from 'react-redux';
4+
import { availablePreferences, availableSkills, formatSkillName } from './FilerData.js';
35
import RankedUserList from './RankedUserList';
46
import styles from './style/CommunityMembersPage.module.css';
5-
import { availableSkills, availablePreferences, formatSkillName } from './FilerData.js';
67

78
function Accordion({ title, children, defaultOpen = false, darkMode }) {
89
const [open, setOpen] = useState(defaultOpen);
@@ -26,6 +27,7 @@ function Accordion({ title, children, defaultOpen = false, darkMode }) {
2627
function CommunityMembersPage() {
2728
const [selectedSkills, setSelectedSkills] = useState([]);
2829
const [selectedPreferences, setSelectedPreferences] = useState([]);
30+
const [searchQuery, setSearchQuery] = useState('');
2931
const darkMode = useSelector(state => state.theme.darkMode);
3032

3133
const toggleItem = (item, selectedArray, setSelectedArray) => {
@@ -44,7 +46,7 @@ function CommunityMembersPage() {
4446
key={skillKey}
4547
onClick={() => toggleItem(skillKey, selectedSkills, setSelectedSkills)}
4648
type="button"
47-
className={`${`${styles.skillButton}`} ${isSelected ? styles.selected : ''}`}
49+
className={`${styles.skillButton} ${isSelected ? styles.selected : ''}`}
4850
>
4951
{formattedName}
5052
</button>
@@ -62,7 +64,7 @@ function CommunityMembersPage() {
6264
key={pref}
6365
onClick={() => toggleItem(pref, selectedPreferences, setSelectedPreferences)}
6466
type="button"
65-
className={`${`${styles.preferenceButton}`} ${isSelected ? styles.selected : ''}`}
67+
className={`${styles.preferenceButton} ${isSelected ? styles.selected : ''}`}
6668
>
6769
{pref}
6870
</button>
@@ -71,10 +73,33 @@ function CommunityMembersPage() {
7173
</div>
7274
);
7375

76+
const hasFilters =
77+
selectedSkills.length > 0 || selectedPreferences.length > 0 || searchQuery.trim().length > 0;
78+
7479
return (
7580
<div className={`${styles.container} ${darkMode ? styles.darkMode : ''}`}>
7681
<h1 className={`${styles.title}`}>Community Member Filters</h1>
7782

83+
{/* Search Bar */}
84+
<div className={`${styles.searchContainer} ${darkMode ? styles.darkSearch : ''}`}>
85+
<input
86+
type="text"
87+
placeholder="Search members by name or skill..."
88+
value={searchQuery}
89+
onChange={e => setSearchQuery(e.target.value)}
90+
className={`${styles.searchInput} ${darkMode ? styles.darkSearchInput : ''}`}
91+
/>
92+
{searchQuery && (
93+
<button
94+
type="button"
95+
className={`${styles.clearButton} ${darkMode ? styles.darkClearButton : ''}`}
96+
onClick={() => setSearchQuery('')}
97+
>
98+
99+
</button>
100+
)}
101+
</div>
102+
78103
<Accordion title="Filter by Skills" defaultOpen darkMode={darkMode}>
79104
{renderSkillButtons()}
80105
</Accordion>
@@ -84,19 +109,26 @@ function CommunityMembersPage() {
84109
</Accordion>
85110

86111
<div>
87-
{selectedSkills.length > 0 || selectedPreferences.length > 0 ? (
112+
{hasFilters ? (
88113
<RankedUserList
89114
selectedSkills={selectedSkills}
90115
selectedPreferences={selectedPreferences}
116+
searchQuery={searchQuery.trim()}
91117
/>
92118
) : (
93119
<p className={`${styles.message}`}>
94-
Select skills or preferences above to see filtered members.
120+
Search or select skills and preferences above to see filtered members.
95121
</p>
96122
)}
97123
</div>
98124
</div>
99125
);
100126
}
127+
Accordion.propTypes = {
128+
title: PropTypes.string.isRequired,
129+
children: PropTypes.node.isRequired,
130+
defaultOpen: PropTypes.bool,
131+
darkMode: PropTypes.bool,
132+
};
101133

102134
export default CommunityMembersPage;
Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
1-
import { useEffect, useState } from 'react';
21
import axios from 'axios';
3-
import UserCard from './UserCard';
2+
import PropTypes from 'prop-types';
3+
import { useEffect, useState } from 'react';
44
import { useSelector } from 'react-redux';
55
import styles from './style/RankedUserList.module.css';
6+
import UserCard from './UserCard';
67

7-
function RankedUserList({ selectedSkills, selectedPreferences }) {
8-
const [rankedUsers, setRankedUsers] = useState([]);
8+
function RankedUserList({ selectedSkills, selectedPreferences, searchQuery }) {
9+
const [allUsers, setAllUsers] = useState([]);
910
const [loading, setLoading] = useState(true);
1011
const darkMode = useSelector(state => state.theme.darkMode);
11-
useEffect(() => {
12-
if (
13-
(!selectedSkills || selectedSkills.length === 0) &&
14-
(!selectedPreferences || selectedPreferences.length === 0)
15-
)
16-
return;
1712

18-
const fetchRankedUsers = async () => {
13+
useEffect(() => {
14+
const fetchUsers = async () => {
1915
setLoading(true);
2016
try {
2117
const params = {};
22-
if (selectedSkills.length > 0) params.skills = selectedSkills.join(',');
23-
if (selectedPreferences.length > 0) params.preferences = selectedPreferences.join(',');
18+
if (selectedSkills && selectedSkills.length > 0) params.skills = selectedSkills.join(',');
19+
if (selectedPreferences && selectedPreferences.length > 0)
20+
params.preferences = selectedPreferences.join(',');
2421

2522
const response = await axios.get(`${process.env.REACT_APP_APIENDPOINT}/hgnform/ranked`, {
2623
params,
2724
});
28-
setRankedUsers(response.data);
25+
setAllUsers(response.data);
2926
} catch (err) {
27+
// error handled silently
3028
} finally {
3129
setLoading(false);
3230
}
3331
};
3432

35-
fetchRankedUsers();
33+
fetchUsers();
34+
// eslint-disable-next-line react-hooks/exhaustive-deps
3635
}, [selectedSkills, selectedPreferences]);
3736

37+
// Client-side filter by searchQuery on top of API results
38+
const filteredUsers = searchQuery
39+
? allUsers.filter(user => {
40+
const name = (user.name || '').toLowerCase();
41+
const skills = (user.topSkills || []).join(' ').toLowerCase();
42+
return (
43+
name.includes(searchQuery.toLowerCase()) || skills.includes(searchQuery.toLowerCase())
44+
);
45+
})
46+
: allUsers;
47+
3848
if (loading) return <p className={`${styles.message}`}>Loading ranked users...</p>;
39-
if (!rankedUsers.length) return <p className={`${styles.message}`}>No users found.</p>;
49+
if (!filteredUsers.length) return <p className={`${styles.message}`}>No users found.</p>;
4050

4151
return (
4252
<div className={darkMode ? `${styles.darkMode}` : ''}>
4353
<div className={`${styles.container}`}>
44-
{rankedUsers.map(user => (
54+
{filteredUsers.map(user => (
4555
<div key={user._id} className={`${styles.userWrapper}`}>
4656
<UserCard user={user} />
4757
</div>
@@ -51,4 +61,10 @@ function RankedUserList({ selectedSkills, selectedPreferences }) {
5161
);
5262
}
5363

64+
RankedUserList.propTypes = {
65+
selectedSkills: PropTypes.arrayOf(PropTypes.string),
66+
selectedPreferences: PropTypes.arrayOf(PropTypes.string),
67+
searchQuery: PropTypes.string,
68+
};
69+
5470
export default RankedUserList;

src/components/HGNHelpSkillsDashboard/style/CommunityMembersPage.module.css

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,49 @@
124124
border-color: #22c55e;
125125
color: #ffffff;
126126
}
127+
128+
.searchContainer {
129+
display: flex;
130+
align-items: center;
131+
margin-bottom: 16px;
132+
border: 1px solid #ccc;
133+
border-radius: 8px;
134+
padding: 6px 12px;
135+
background: #fff;
136+
}
137+
138+
.searchInput {
139+
flex: 1;
140+
border: none;
141+
outline: none;
142+
font-size: 14px;
143+
background: transparent;
144+
color: #000;
145+
}
146+
147+
.clearButton {
148+
background: none;
149+
border: none;
150+
cursor: pointer;
151+
color: #888;
152+
font-size: 14px;
153+
padding: 0 4px;
154+
}
155+
156+
.darkSearch {
157+
border-color: #555;
158+
background: #2d3748;
159+
}
160+
161+
.darkSearchInput {
162+
color: #fff !important;
163+
background: transparent;
164+
}
165+
166+
.darkSearch .clearButton {
167+
color: #ccc;
168+
}
169+
170+
.darkClearButton {
171+
color: #f2e8e8;
172+
}

0 commit comments

Comments
 (0)