11import React , { useState , useEffect } from "react" ;
22import {
3- SimpleGrid ,
4- Heading ,
5- Spinner ,
6- Text ,
7- VStack ,
8- Input ,
9- InputGroup ,
10- InputLeftElement ,
11- Select ,
3+ SimpleGrid ,
4+ Heading ,
5+ Spinner ,
6+ Text ,
7+ VStack ,
8+ Input ,
9+ InputGroup ,
10+ InputLeftElement ,
11+ Select ,
1212} from "@chakra-ui/react" ;
1313import { SearchIcon } from "@chakra-ui/icons" ;
1414import { MemberCard } from "../components/MemberCard" ;
@@ -18,146 +18,145 @@ import { Layout } from "../components/Layout";
1818import { Helmet } from "react-helmet-async" ;
1919
2020enum SearchBy {
21- FIRST_NAME = "First Name" ,
22- LAST_NAME = "Last Name" ,
23- EMAIL = "Email" ,
24- DISCORD = "Discord" ,
21+ FIRST_NAME = "First Name" ,
22+ LAST_NAME = "Last Name" ,
2523}
2624
2725const leadSortingFunction = ( a : IMember , b : IMember ) => {
28- if ( a . lead === b . lead ) return 0 ;
29- return a . lead ? - 1 : 1 ;
26+ if ( a . lead === b . lead ) return 0 ;
27+ return a . lead ? - 1 : 1 ;
3028} ;
3129
3230// Taking in an object for readability's sake when rendering
3331const renderMembers = ( data : { members : IMember [ ] ; isLead : boolean } ) => {
34- const { members, isLead } = data ;
32+ const { members, isLead } = data ;
3533
36- const filteredMembers = members . filter ( ( member ) => member . lead === isLead ) ;
34+ const processedMembers = members
35+ . filter ( ( member ) => member . lead === isLead )
36+ . sort ( ( a , b ) => a . firstName . localeCompare ( b . firstName ) ) ;
3737
38- if ( filteredMembers . length === 0 ) {
39- return < > </ > ;
40- }
41- return (
42- < >
43- < Text fontSize = "xl" fontWeight = "bold" >
44- { isLead ? "Team Leads" : "All Members" }
45- </ Text >
46- < SimpleGrid columns = { [ 1 , 2 , 3 , 4 ] } spacing = { 6 } >
47- { filteredMembers . map ( ( member ) => (
48- < MemberCard key = { member . memberId ?. toString ( ) } member = { member } />
49- ) ) }
50- </ SimpleGrid >
51- </ >
52- ) ;
38+ if ( processedMembers . length === 0 ) {
39+ return < > </ > ;
40+ }
41+ return (
42+ < >
43+ < Text fontSize = "xl" fontWeight = "bold" >
44+ { isLead ? "Team Leads" : "All Members" }
45+ </ Text >
46+ < SimpleGrid columns = { [ 1 , 2 , 3 , 4 ] } spacing = { 6 } >
47+ { processedMembers . map ( ( member ) => (
48+ < MemberCard
49+ key = { member . memberId ?. toString ( ) }
50+ member = { member }
51+ />
52+ ) ) }
53+ </ SimpleGrid >
54+ </ >
55+ ) ;
5356} ;
5457
5558export const MembersPage : React . FC = ( ) => {
56- const [ members , setMembers ] = useState < IMember [ ] > ( [ ] ) ;
57- const [ filteredMembers , setFilteredMembers ] = useState < IMember [ ] > ( [ ] ) ;
58- const [ loading , setLoading ] = useState ( true ) ;
59- const [ error , setError ] = useState < string | null > ( null ) ;
60- const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
61- const [ searchBy , setSearchBy ] = useState < SearchBy > ( SearchBy . FIRST_NAME ) ;
59+ const [ members , setMembers ] = useState < IMember [ ] > ( [ ] ) ;
60+ const [ filteredMembers , setFilteredMembers ] = useState < IMember [ ] > ( [ ] ) ;
61+ const [ loading , setLoading ] = useState ( true ) ;
62+ const [ error , setError ] = useState < string | null > ( null ) ;
63+ const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
64+ const [ searchBy , setSearchBy ] = useState < SearchBy > ( SearchBy . FIRST_NAME ) ;
6265
63- useEffect ( ( ) => {
64- const fetchMembers = async ( ) => {
65- try {
66- const fetchedMembers = await memberService . getAllMembers ( ) ;
67- fetchedMembers . sort ( leadSortingFunction ) ;
68- setMembers ( fetchedMembers ) ;
69- setFilteredMembers ( fetchedMembers ) ;
70- } catch ( err ) {
71- setError ( "Failed to fetch members" ) ;
72- } finally {
73- setLoading ( false ) ;
74- }
75- } ;
66+ useEffect ( ( ) => {
67+ const fetchMembers = async ( ) => {
68+ try {
69+ const fetchedMembers = await memberService . getAllMembers ( ) ;
70+ fetchedMembers . sort ( leadSortingFunction ) ;
71+ setMembers ( fetchedMembers ) ;
72+ setFilteredMembers ( fetchedMembers ) ;
73+ } catch ( err ) {
74+ setError ( "Failed to fetch members" ) ;
75+ } finally {
76+ setLoading ( false ) ;
77+ }
78+ } ;
7679
77- fetchMembers ( ) ;
78- } , [ ] ) ;
80+ fetchMembers ( ) ;
81+ } , [ ] ) ;
7982
80- useEffect ( ( ) => {
81- let results ;
82- switch ( searchBy ) {
83- case SearchBy . FIRST_NAME :
84- results = members . filter ( ( member ) =>
85- member . firstName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
86- ) ;
87- break ;
88- case SearchBy . LAST_NAME :
89- results = members . filter ( ( member ) =>
90- member . lastName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
91- ) ;
92- break ;
93- case SearchBy . EMAIL :
94- results = members . filter ( ( member ) =>
95- member . email . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
83+ useEffect ( ( ) => {
84+ let results ;
85+ switch ( searchBy ) {
86+ case SearchBy . FIRST_NAME :
87+ results = members . filter ( ( member ) =>
88+ member . firstName
89+ . toLowerCase ( )
90+ . includes ( searchTerm . toLowerCase ( ) )
91+ ) ;
92+ break ;
93+ case SearchBy . LAST_NAME :
94+ results = members . filter ( ( member ) =>
95+ member . lastName
96+ . toLowerCase ( )
97+ . includes ( searchTerm . toLowerCase ( ) )
98+ ) ;
99+ break ;
100+ default :
101+ results = members ;
102+ }
103+ results . sort ( leadSortingFunction ) ;
104+ setFilteredMembers ( results ) ;
105+ } , [ searchTerm , members , searchBy ] ) ;
106+
107+ if ( loading )
108+ return (
109+ < Layout >
110+ < Spinner size = "xl" />
111+ </ Layout >
96112 ) ;
97- break ;
98- case SearchBy . DISCORD :
99- results = members . filter ( ( member ) =>
100- member . discord . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
113+ if ( error )
114+ return (
115+ < Layout >
116+ < Text color = "red.500" > { error } </ Text >
117+ </ Layout >
101118 ) ;
102- break ;
103- default :
104- results = members ;
105- }
106- results . sort ( leadSortingFunction ) ;
107- setFilteredMembers ( results ) ;
108- } , [ searchTerm , members , searchBy ] ) ;
109119
110- if ( loading )
111120 return (
112- < Layout >
113- < Spinner size = "xl" />
114- </ Layout >
121+ < Layout >
122+ < Helmet >
123+ < title > Husky Coding Project Members</ title >
124+ < meta
125+ name = "description"
126+ content = "Meet the talented members of Husky Coding Project — a community of developers, designers, and innovators at the University of Washington. Learn more about our team leads and members who collaboratively build impactful software projects, advance their technical skills, and drive innovation through teamwork and creativity."
127+ />
128+ </ Helmet >
129+ < VStack spacing = { 8 } align = "stretch" >
130+ < Heading > Members</ Heading >
131+ < InputGroup >
132+ < InputLeftElement
133+ pointerEvents = "none"
134+ children = { < SearchIcon color = "gray.300" /> }
135+ />
136+ < Input
137+ type = "text"
138+ placeholder = "Search members..."
139+ value = { searchTerm }
140+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
141+ />
142+ < Select
143+ value = { searchBy }
144+ onChange = { ( e ) =>
145+ setSearchBy ( e . target . value as SearchBy )
146+ }
147+ w = "15%"
148+ paddingLeft = { 6 }
149+ >
150+ { Object . values ( SearchBy ) . map ( ( value ) => (
151+ < option key = { value } value = { value } >
152+ { value }
153+ </ option >
154+ ) ) }
155+ </ Select >
156+ </ InputGroup >
157+ { renderMembers ( { members : filteredMembers , isLead : true } ) }
158+ { renderMembers ( { members : filteredMembers , isLead : false } ) }
159+ </ VStack >
160+ </ Layout >
115161 ) ;
116- if ( error )
117- return (
118- < Layout >
119- < Text color = "red.500" > { error } </ Text >
120- </ Layout >
121- ) ;
122-
123- return (
124- < Layout >
125- < Helmet >
126- < title > Husky Coding Project Members</ title >
127- < meta
128- name = "description"
129- content = "Meet the talented members of Husky Coding Project — a community of developers, designers, and innovators at the University of Washington. Learn more about our team leads and members who collaboratively build impactful software projects, advance their technical skills, and drive innovation through teamwork and creativity."
130- />
131- </ Helmet >
132- < VStack spacing = { 8 } align = "stretch" >
133- < Heading > Members</ Heading >
134- < InputGroup >
135- < InputLeftElement
136- pointerEvents = "none"
137- children = { < SearchIcon color = "gray.300" /> }
138- />
139- < Input
140- type = "text"
141- placeholder = "Search members..."
142- value = { searchTerm }
143- onChange = { ( e ) => setSearchTerm ( e . target . value ) }
144- />
145- < Select
146- value = { searchBy }
147- onChange = { ( e ) => setSearchBy ( e . target . value as SearchBy ) }
148- w = "15%"
149- paddingLeft = { 6 }
150- >
151- { Object . values ( SearchBy ) . map ( ( value ) => (
152- < option key = { value } value = { value } >
153- { value }
154- </ option >
155- ) ) }
156- </ Select >
157- </ InputGroup >
158- { renderMembers ( { members : filteredMembers , isLead : true } ) }
159- { renderMembers ( { members : filteredMembers , isLead : false } ) }
160- </ VStack >
161- </ Layout >
162- ) ;
163162} ;
0 commit comments