Skip to content

Commit 1daa453

Browse files
Merge pull request #4503 from OneCommunityGlobal/aryan_show_team_members
Aryan Updated team members components
2 parents 933fcee + 9b679f7 commit 1daa453

4 files changed

Lines changed: 121 additions & 189 deletions

File tree

src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx

Lines changed: 14 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,43 @@
1-
import { useEffect, useMemo, useState } from 'react';
2-
import axios from 'axios';
1+
import React, { useState, useMemo } from 'react';
32
import RankedUserList from './RankedUserList';
4-
import styles from './style/CommunityMembersPage.module.css';
53

64
const availableSkills = ['React', 'Redux', 'HTML', 'CSS', 'MongoDB', 'Database', 'Agile'];
7-
const RANKED_USERS_ENDPOINT = 'http://localhost:4500/api/hgnform/ranked';
85

96
function CommunityMembersPage() {
107
const [selectedSkills, setSelectedSkills] = useState([]);
11-
const [searchTerm, setSearchTerm] = useState('');
12-
const [sortOrder, setSortOrder] = useState('asc');
13-
const [showFilters, setShowFilters] = useState(false);
14-
const [rankedUsers, setRankedUsers] = useState([]);
15-
const [loading, setLoading] = useState(true);
16-
const [error, setError] = useState(null);
17-
18-
useEffect(() => {
19-
const fetchRankedUsers = async () => {
20-
setLoading(true);
21-
try {
22-
const params = {
23-
skills: (selectedSkills.length ? selectedSkills : availableSkills).join(','),
24-
};
25-
const response = await axios.get(RANKED_USERS_ENDPOINT, { params });
26-
setRankedUsers(response.data);
27-
setError(null);
28-
} catch (err) {
29-
setError('Unable to load community members right now. Please try again later.');
30-
} finally {
31-
setLoading(false);
32-
}
33-
};
34-
35-
fetchRankedUsers();
36-
}, [selectedSkills]);
378

389
const handleCheckboxChange = skill => {
3910
setSelectedSkills(prev =>
4011
prev.includes(skill) ? prev.filter(s => s !== skill) : [...prev, skill],
4112
);
4213
};
4314

44-
const toggleSortOrder = () => {
45-
setSortOrder(prev => (prev === 'asc' ? 'desc' : 'asc'));
46-
};
47-
48-
const clearFilters = () => {
49-
setSelectedSkills([]);
50-
};
51-
52-
const filteredUsers = useMemo(() => {
53-
const normalizedSearch = searchTerm.trim().toLowerCase();
54-
const normalizedSelectedSkills = selectedSkills.map(skill => skill.toLowerCase());
55-
let result = rankedUsers;
56-
57-
if (normalizedSearch) {
58-
result = rankedUsers.filter(user => {
59-
const nameMatches = user.name?.toLowerCase().includes(normalizedSearch);
60-
const skillMatches = Array.isArray(user.topSkills)
61-
? user.topSkills.some(skill => skill.toLowerCase().includes(normalizedSearch))
62-
: false;
63-
return nameMatches || skillMatches;
64-
});
65-
}
66-
67-
if (normalizedSelectedSkills.length) {
68-
result = result.filter(user => {
69-
if (!Array.isArray(user.topSkills) || user.topSkills.length === 0) return false;
70-
return user.topSkills.some(skill =>
71-
normalizedSelectedSkills.includes((skill || '').toLowerCase()),
72-
);
73-
});
74-
}
75-
76-
return [...result].sort((a, b) => {
77-
const first = a.name || '';
78-
const second = b.name || '';
79-
return sortOrder === 'asc' ? first.localeCompare(second) : second.localeCompare(first);
80-
});
81-
}, [rankedUsers, searchTerm, sortOrder, selectedSkills]);
82-
83-
const emptyMessage =
84-
searchTerm || selectedSkills.length
85-
? 'No community members match your current filters.'
86-
: 'No community members available yet.';
15+
// EFFECTIVE SKILLS = what we pass to RankedUserList
16+
const effectiveSkills = useMemo(() => {
17+
return selectedSkills.length > 0 ? selectedSkills : availableSkills;
18+
}, [selectedSkills]);
8719

8820
return (
89-
<div className={styles.container}>
90-
<h1 className={styles.heading}>One Community Members</h1>
91-
<div className={styles.controlsRow}>
92-
<div className={styles.searchWrapper}>
93-
<input
94-
type="search"
95-
value={searchTerm}
96-
onChange={event => setSearchTerm(event.target.value)}
97-
placeholder="Search by team member name or skills"
98-
className={styles.searchInput}
99-
aria-label="Search community members"
100-
/>
101-
</div>
102-
<button
103-
type="button"
104-
className={styles.filterButton}
105-
onClick={() => setShowFilters(prev => !prev)}
106-
>
107-
{showFilters ? 'Hide Filters' : 'Filter'}
108-
</button>
109-
<button type="button" className={styles.sortButton} onClick={toggleSortOrder}>
110-
{sortOrder === 'asc' ? 'A→Z Sort' : 'Z→A Sort'}
111-
</button>
112-
{(selectedSkills.length > 0 || searchTerm) && (
113-
<button
114-
type="button"
115-
className={styles.clearButton}
116-
onClick={() => {
117-
clearFilters();
118-
setSearchTerm('');
119-
}}
120-
>
121-
Clear All
122-
</button>
123-
)}
124-
</div>
21+
<div>
22+
<h1>Community Members</h1>
12523

126-
{showFilters && (
127-
<div className={styles.filtersPanel}>
24+
<div style={{ marginBottom: 16 }}>
25+
<strong>Filter by skills:</strong>
26+
<div style={{ display: 'flex', gap: 10, marginTop: 8, flexWrap: 'wrap' }}>
12827
{availableSkills.map(skill => (
129-
<label key={skill} className={styles.filterOption}>
28+
<label key={skill} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
13029
<input
13130
type="checkbox"
13231
checked={selectedSkills.includes(skill)}
13332
onChange={() => handleCheckboxChange(skill)}
13433
/>
135-
<span>{skill}</span>
34+
{skill}
13635
</label>
13736
))}
13837
</div>
139-
)}
140-
141-
<p className={styles.helperText}>
142-
When multiple filters are selected, the score represents the average value, and the options
143-
are ranked based on their scoring. Click each profile to learn more details.
144-
</p>
38+
</div>
14539

146-
<RankedUserList
147-
users={filteredUsers}
148-
loading={loading}
149-
error={error}
150-
emptyMessage={emptyMessage}
151-
/>
40+
<RankedUserList selectedSkills={effectiveSkills} />
15241
</div>
15342
);
15443
}
Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,61 @@
1-
import PropTypes from 'prop-types';
1+
import { useEffect, useState } from 'react';
2+
import axios from 'axios';
23
import UserCard from './UserCard';
3-
import styles from './style/UserCard.module.css';
4+
import './style/UserCard.module.css';
45

5-
function RankedUserList({ users, loading, error, emptyMessage }) {
6-
if (loading) {
7-
return <p>Loading community members...</p>;
8-
}
6+
function RankedUserList({ selectedSkills = [] }) {
7+
const [rankedUsers, setRankedUsers] = useState([]);
8+
const [loading, setLoading] = useState(true);
9+
const [error, setError] = useState(null);
910

10-
if (error) {
11-
return <p className="text-danger">{error}</p>;
12-
}
11+
useEffect(() => {
12+
let canceled = false;
1313

14-
if (!users.length) {
15-
return <p>{emptyMessage}</p>;
16-
}
14+
const fetchRankedUsers = async () => {
15+
setLoading(true);
16+
setError(null);
17+
18+
try {
19+
const params = {};
20+
if (Array.isArray(selectedSkills) && selectedSkills.length > 0) {
21+
params.skills = selectedSkills.join(',');
22+
}
23+
24+
const response = await axios.get('http://localhost:4500/api/hgnform/ranked', { params });
25+
26+
if (!canceled) {
27+
const data = response?.data || [];
28+
setRankedUsers(Array.isArray(data) ? data : []);
29+
}
30+
} catch (err) {
31+
console.error('Failed to fetch ranked users', err);
32+
if (!canceled) {
33+
setError(err);
34+
setRankedUsers([]);
35+
}
36+
} finally {
37+
if (!canceled) setLoading(false);
38+
}
39+
};
40+
41+
fetchRankedUsers();
42+
43+
return () => {
44+
canceled = true;
45+
};
46+
}, [selectedSkills]);
47+
48+
if (loading) return <p>Loading ranked users...</p>;
49+
if (error) return <p>Failed to load users.</p>;
50+
if (!rankedUsers.length) return <p>No members found.</p>;
1751

1852
return (
19-
<div className={styles.containerGrid}>
20-
{users.map(user => (
21-
<UserCard key={user._id || user.email || user.name} user={user} />
53+
<div className="user-card-container">
54+
{rankedUsers.map(user => (
55+
<UserCard key={user._id || user.id || user.uuid} user={user} />
2256
))}
2357
</div>
2458
);
2559
}
2660

27-
RankedUserList.propTypes = {
28-
users: PropTypes.arrayOf(PropTypes.object).isRequired,
29-
loading: PropTypes.bool.isRequired,
30-
error: PropTypes.string,
31-
emptyMessage: PropTypes.string.isRequired,
32-
};
33-
34-
RankedUserList.defaultProps = {
35-
error: null,
36-
};
37-
3861
export default RankedUserList;

src/components/HGNHelpSkillsDashboard/UserCard.jsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1+
import React from 'react';
12
import styles from './style/UserCard.module.css';
23
import avatar from './style/avatar.png';
34
import emailIcon from './style/email_icon.png';
45
import slackIcon from './style/slack_icon.png';
56

7+
import { useSelector } from 'react-redux';
8+
69
function UserCard({ user }) {
7-
const { name, email, slack, score, topSkills = [] } = user;
10+
const darkMode = useSelector(state => state.theme?.darkMode);
11+
const { name, email, slack, score, topSkills } = user;
812

913
const getScoreColor = userScore => {
1014
if (userScore >= 5) return '#00754A';
1115
return '#D93D3D';
1216
};
1317

1418
return (
15-
<div className={`${styles.userCard}`}>
19+
<div className={`${styles.userCard} ${darkMode ? styles.dark : ''}`}>
1620
<img src={avatar} alt="Avatar" className={`${styles.avatar}`} />
1721
<div className={`${styles.info}`}>
18-
<div className={`${styles.userName}`}>{name}</div>
22+
<div className={`${styles.userName}`} title={name}>
23+
{name}
24+
</div>
1925
{email && (
2026
<div className={`${styles.contactLine}`}>
2127
<img src={emailIcon} alt="Email" className={`${styles.contactIcon}`} />
@@ -41,7 +47,9 @@ function UserCard({ user }) {
4147

4248
<div className={`${styles.skillsSection}`}>
4349
<div className={`${styles.skillsLabel}`}>Top Skills:</div>
44-
<div className={`${styles.skillsText}`}>{topSkills.join(', ')}</div>
50+
<div className={`${styles.skillsText}`}>
51+
{Array.isArray(topSkills) ? topSkills.join(', ') : topSkills || ''}
52+
</div>
4553
</div>
4654
</div>
4755
</div>

0 commit comments

Comments
 (0)