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,138 +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" ,
21+ FIRST_NAME = "First Name" ,
22+ LAST_NAME = "Last Name" ,
2323}
2424
2525const leadSortingFunction = ( a : IMember , b : IMember ) => {
26- if ( a . lead === b . lead ) return 0 ;
27- return a . lead ? - 1 : 1 ;
26+ if ( a . lead === b . lead ) return 0 ;
27+ return a . lead ? - 1 : 1 ;
2828} ;
2929
3030// Taking in an object for readability's sake when rendering
3131const renderMembers = ( data : { members : IMember [ ] ; isLead : boolean } ) => {
32- const { members, isLead } = data ;
32+ const { members, isLead } = data ;
3333
34- const processedMembers = members . filter ( ( member ) => member . lead === isLead ) . sort ( ( a , b ) => {
35- if ( a . firstName < b . firstName ) return - 1 ;
36- if ( a . firstName > b . firstName ) return 1 ;
37- return 0 ;
38- } ) ;
34+ const processedMembers = members
35+ . filter ( ( member ) => member . lead === isLead )
36+ . sort ( ( a , b ) => a . firstName . localeCompare ( b . firstName ) ) ;
3937
40- if ( processedMembers . length === 0 ) {
41- return < > </ > ;
42- }
43- return (
44- < >
45- < Text fontSize = "xl" fontWeight = "bold" >
46- { isLead ? "Team Leads" : "All Members" }
47- </ Text >
48- < SimpleGrid columns = { [ 1 , 2 , 3 , 4 ] } spacing = { 6 } >
49- { processedMembers . map ( ( member ) => (
50- < MemberCard key = { member . memberId ?. toString ( ) } member = { member } />
51- ) ) }
52- </ SimpleGrid >
53- </ >
54- ) ;
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+ ) ;
5556} ;
5657
5758export const MembersPage : React . FC = ( ) => {
58- const [ members , setMembers ] = useState < IMember [ ] > ( [ ] ) ;
59- const [ filteredMembers , setFilteredMembers ] = useState < IMember [ ] > ( [ ] ) ;
60- const [ loading , setLoading ] = useState ( true ) ;
61- const [ error , setError ] = useState < string | null > ( null ) ;
62- const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
63- 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 ) ;
65+
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+ } ;
6479
65- useEffect ( ( ) => {
66- const fetchMembers = async ( ) => {
67- try {
68- const fetchedMembers = await memberService . getAllMembers ( ) ;
69- fetchedMembers . sort ( leadSortingFunction ) ;
70- setMembers ( fetchedMembers ) ;
71- setFilteredMembers ( fetchedMembers ) ;
72- } catch ( err ) {
73- setError ( "Failed to fetch members" ) ;
74- } finally {
75- setLoading ( false ) ;
76- }
77- } ;
80+ fetchMembers ( ) ;
81+ } , [ ] ) ;
7882
79- fetchMembers ( ) ;
80- } , [ ] ) ;
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 ] ) ;
81106
82- useEffect ( ( ) => {
83- let results ;
84- switch ( searchBy ) {
85- case SearchBy . FIRST_NAME :
86- results = members . filter ( ( member ) =>
87- member . firstName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
107+ if ( loading )
108+ return (
109+ < Layout >
110+ < Spinner size = "xl" />
111+ </ Layout >
88112 ) ;
89- break ;
90- case SearchBy . LAST_NAME :
91- results = members . filter ( ( member ) =>
92- member . lastName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
113+ if ( error )
114+ return (
115+ < Layout >
116+ < Text color = "red.500" > { error } </ Text >
117+ </ Layout >
93118 ) ;
94- break ;
95- default :
96- results = members ;
97- }
98- results . sort ( leadSortingFunction ) ;
99- setFilteredMembers ( results ) ;
100- } , [ searchTerm , members , searchBy ] ) ;
101119
102- if ( loading )
103- return (
104- < Layout >
105- < Spinner size = "xl" />
106- </ Layout >
107- ) ;
108- if ( error )
109120 return (
110- < Layout >
111- < Text color = "red.500" > { error } </ Text >
112- </ 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 >
113161 ) ;
114-
115- return (
116- < Layout >
117- < Helmet >
118- < title > Husky Coding Project Members</ title >
119- < meta
120- name = "description"
121- 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."
122- />
123- </ Helmet >
124- < VStack spacing = { 8 } align = "stretch" >
125- < Heading > Members</ Heading >
126- < InputGroup >
127- < InputLeftElement
128- pointerEvents = "none"
129- children = { < SearchIcon color = "gray.300" /> }
130- />
131- < Input
132- type = "text"
133- placeholder = "Search members..."
134- value = { searchTerm }
135- onChange = { ( e ) => setSearchTerm ( e . target . value ) }
136- />
137- < Select
138- value = { searchBy }
139- onChange = { ( e ) => setSearchBy ( e . target . value as SearchBy ) }
140- w = "15%"
141- paddingLeft = { 6 }
142- >
143- { Object . values ( SearchBy ) . map ( ( value ) => (
144- < option key = { value } value = { value } >
145- { value }
146- </ option >
147- ) ) }
148- </ Select >
149- </ InputGroup >
150- { renderMembers ( { members : filteredMembers , isLead : true } ) }
151- { renderMembers ( { members : filteredMembers , isLead : false } ) }
152- </ VStack >
153- </ Layout >
154- ) ;
155162} ;
0 commit comments