Skip to content

Commit a4a75aa

Browse files
authored
Merge pull request #19 from BenjaminSoYH/main
Team Leads page
2 parents 40ac59f + 9b3b433 commit a4a75aa

File tree

8 files changed

+194
-67
lines changed

8 files changed

+194
-67
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@
5454
"vite": "^5.3.4",
5555
"vite-plugin-singlefile": "^2.0.2"
5656
}
57-
}
57+
}

src/components/MemberCard.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
useColorModeValue,
88
Badge,
99
} from '@chakra-ui/react';
10+
import { FaDiscord, FaLinkedin, FaEnvelope } from 'react-icons/fa';
11+
import { Link as RouterLink, useNavigate } from 'react-router-dom';
12+
import { COLOR_MAP, IMember } from '../interfaces/IMember';
1013
import { useNavigate } from 'react-router-dom';
1114
import { IMember } from '../interfaces/IMember';
1215
import { Team } from '../interfaces/DBTypes';
@@ -15,27 +18,15 @@ interface MemberCardProps {
1518
member: IMember;
1619
}
1720

18-
const COLOR_MAP: Record<Team, string> = {
19-
communications: 'red',
20-
design: 'blue',
21-
finance: 'green',
22-
education: 'purple',
23-
onboarding: 'pink',
24-
tech: 'yellow',
25-
'co-chair': 'orange',
26-
};
27-
2821
export const MemberCard: React.FC<MemberCardProps> = ({ member }) => {
2922
const bgColor = useColorModeValue('white', 'gray.800');
30-
// const borderColor = useColorModeValue('gray.200', 'gray.600');
3123
const borderColor = useColorModeValue('gray.200', 'gray.600');
3224
const borderWidth = member.lead ? '2px' : '1px';
3325

3426
const navigate = useNavigate();
3527
return (
3628
<Box
3729
onClick={() => navigate(`/members/${member.memberId}`)}
38-
// borderWidth="1px"
3930
borderWidth={borderWidth}
4031
borderRadius="lg"
4132
p={6}

src/components/TeamLeadCard.tsx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useState } from 'react'
2+
import {
3+
Box,
4+
Text,
5+
VStack,
6+
Button,
7+
Modal,
8+
ModalOverlay,
9+
ModalContent,
10+
ModalHeader,
11+
ModalBody,
12+
ModalCloseButton,
13+
ModalFooter,
14+
useDisclosure,
15+
Heading,
16+
List,
17+
ListItem,
18+
useColorModeValue,
19+
HStack,
20+
Avatar
21+
} from '@chakra-ui/react'
22+
import { motion } from 'framer-motion'
23+
import { IMember, COLOR_MAP } from '../interfaces/IMember'
24+
import { memberService } from '../service/memberService'
25+
26+
import { Team } from '../interfaces/DBTypes'
27+
28+
interface TeamCardProps {
29+
team: Team
30+
}
31+
32+
const MotionBox = motion(Box)
33+
34+
const formatTeamName = (team: string): string => {
35+
return team[0].toUpperCase() + team.slice(1) + ' Team'
36+
};
37+
38+
export const TeamLeadCard: React.FC<TeamCardProps> = ({team}) => {
39+
40+
const TEAM_NAME = formatTeamName(team)
41+
42+
const { isOpen, onOpen, onClose } = useDisclosure()
43+
const [teamMembers, setTeamMembers] = useState<IMember[]>([])
44+
const [isLoading, setIsLoading] = useState(false)
45+
const [isError, setError] = useState(false)
46+
47+
const teamColor = COLOR_MAP[team]
48+
const borderColor = useColorModeValue(`${teamColor}.300`, `${teamColor}.500`)
49+
const bgColor = useColorModeValue('white', 'gray.800')
50+
51+
const handleClick = async () => {
52+
try {
53+
setIsLoading(true)
54+
const teamLeads = await memberService.getMembersbyTeam(team)
55+
56+
setTeamMembers(teamLeads)
57+
setError(false)
58+
onOpen()
59+
60+
} catch (error) {
61+
console.error('Error fetching team members:', error)
62+
setError(true)
63+
} finally {
64+
setIsLoading(false)
65+
}
66+
}
67+
68+
return (
69+
<>
70+
<MotionBox
71+
borderWidth='1px'
72+
borderRadius='lg'
73+
p={6}
74+
boxShadow='md'
75+
cursor='pointer'
76+
whileHover={{ scale: 1.05 }}
77+
whileTap={{ scale: 0.95 }}
78+
transition={{ duration: 0.2 }}
79+
onClick={handleClick}
80+
bg={bgColor}
81+
borderColor={borderColor}
82+
>
83+
<VStack spacing={4} align='center'>
84+
<Text fontWeight='bold' fontSize='xl'>
85+
{isLoading ? 'Loading team leads...' : TEAM_NAME}
86+
</Text>
87+
{isError && <Text color='red.500'>Error loading team leads</Text>}
88+
89+
</VStack>
90+
</MotionBox>
91+
92+
<Modal isOpen={isOpen && !isLoading} onClose={onClose}>
93+
<ModalOverlay />
94+
<ModalContent>
95+
<ModalHeader>{TEAM_NAME}</ModalHeader>
96+
<ModalCloseButton />
97+
<ModalBody>
98+
<VStack align='start' spacing={4}>
99+
{/* <Heading size='md'>Team Members</Heading> */}
100+
<List spacing={3} width='100%'>
101+
{teamMembers.map(member => (
102+
<ListItem key={member.memberId?.toString()}>
103+
<HStack>
104+
<Avatar
105+
size='sm'
106+
name={`${member.firstName} ${member.lastName}`}
107+
/>
108+
<Text>
109+
{member.firstName} {member.lastName}
110+
</Text>
111+
<Text fontSize='sm' color='gray.500'>
112+
{member.email}
113+
</Text>
114+
</HStack>
115+
</ListItem>
116+
))}
117+
</List>
118+
{teamMembers.length === 0 && <Text>No team members found.</Text>}
119+
</VStack>
120+
</ModalBody>
121+
<ModalFooter>
122+
<Button colorScheme='blue' mr={3} onClick={onClose}>
123+
Close
124+
</Button>
125+
</ModalFooter>
126+
</ModalContent>
127+
</Modal>
128+
</>
129+
)
130+
}

src/interfaces/DBTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const VALID_TEAMS = [
1+
export const VALID_TEAMS = [
22
'onboarding',
33
'education',
44
'tech',

src/interfaces/IMember.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,14 @@ export interface IMember {
1313
lead: boolean;
1414
team: Team;
1515
}
16+
17+
export const COLOR_MAP: Record<Team, string> = {
18+
communications: 'red',
19+
design: 'blue',
20+
finance: 'green',
21+
education: 'purple',
22+
onboarding: 'pink',
23+
tech: 'yellow',
24+
'co-chair': 'orange',
25+
};
26+

src/pages/LeadTeamsPage.tsx

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,16 @@
1-
import React, { useState, useEffect } from 'react';
2-
import { SimpleGrid, Heading, Spinner, Text, VStack, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
3-
import { SearchIcon } from '@chakra-ui/icons';
4-
import { TeamCard } from '../components/TeamCard';
5-
import { teamService } from '../service/teamService';
6-
import { ITeam } from '../interfaces/ITeam';
1+
import { SimpleGrid, Heading, VStack } from '@chakra-ui/react';
2+
import { TeamLeadCard } from '../components/TeamLeadCard';
73
import { Layout } from '../components/Layout';
4+
import { VALID_TEAMS } from '../interfaces/DBTypes';
85

96
export const LeadTeamsPage: React.FC = () => {
10-
const [teams, setTeams] = useState<ITeam[]>([]);
11-
const [filteredTeams, setFilteredTeams] = useState<ITeam[]>([]);
12-
const [loading, setLoading] = useState(true);
13-
const [error, setError] = useState<string | undefined>(undefined);
14-
const [searchTerm, setSearchTerm] = useState('');
15-
16-
useEffect(() => {
17-
const fetchTeams = async () => {
18-
try {
19-
const fetchedTeams = await teamService.getAllTeams(true);
20-
setTeams(fetchedTeams);
21-
setFilteredTeams(fetchedTeams);
22-
} catch (err) {
23-
console.error(err);
24-
setError('Failed to fetch teams');
25-
} finally {
26-
setLoading(false);
27-
}
28-
};
29-
30-
fetchTeams();
31-
}, []);
32-
33-
useEffect(() => {
34-
const results = teams.filter(team =>
35-
team.name?.toLowerCase().includes(searchTerm.toLowerCase())
36-
);
37-
setFilteredTeams(results);
38-
}, [searchTerm, teams]);
39-
40-
if (loading) return <Layout><Spinner size="xl" /></Layout>;
41-
if (error) return <Layout><Text color="red.500">{error}</Text></Layout>;
42-
437
return (
448
<Layout>
459
<VStack spacing={8} align="stretch">
46-
<Heading>Teams</Heading>
47-
<InputGroup>
48-
<InputLeftElement pointerEvents="none" children={<SearchIcon color="gray.300" />} />
49-
<Input
50-
type="text"
51-
placeholder="Search teams..."
52-
value={searchTerm}
53-
onChange={(e) => setSearchTerm(e.target.value)}
54-
/>
55-
</InputGroup>
10+
<Heading>Leadership Teams</Heading>
5611
<SimpleGrid columns={[1, 2, 3]} spacing={6}>
57-
{filteredTeams.map(team => (
58-
<TeamCard key={team.teamId?.toString()} team={team} />
12+
{VALID_TEAMS.map((team, i) => (
13+
<TeamLeadCard key={i} team={team} />
5914
))}
6015
</SimpleGrid>
6116
</VStack>

src/service/memberService.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { IMember } from '../interfaces/IMember';
22
import { supabase } from './client';
3-
import { DBMember } from '../interfaces/DBTypes';
3+
import { DBMember, Team} from '../interfaces/DBTypes';
44
import { mapDBMemberToIMember } from '../interfaces/mapping';
55

6+
67
export const memberService = {
78
getAllMembers: async (): Promise<IMember[]> => {
89
const { data, error } = await supabase
@@ -51,4 +52,43 @@ export const memberService = {
5152
deleteMember: async (_id: bigint): Promise<void> => {
5253
throw new Error('Not implemented');
5354
},
55+
56+
getAllLeads: async (): Promise<IMember[]> => {
57+
const { data, error } = await supabase
58+
.from('Members')
59+
.select('*')
60+
.eq('lead', true)
61+
.returns<DBMember[]>();
62+
63+
if (error) {
64+
throw new Error(error.message);
65+
}
66+
67+
if (!data) {
68+
return [];
69+
}
70+
71+
return data.map(mapDBMemberToIMember);
72+
},
73+
74+
getMembersbyTeam: async (teamlead: Team): Promise<IMember[]> => {
75+
const { data, error } = await supabase
76+
.from('Members')
77+
.select('*')
78+
.eq('teamleads', teamlead)
79+
.returns<DBMember[]>()
80+
81+
if (error) {
82+
throw new Error(error.message);
83+
}
84+
85+
if (!data) {
86+
throw new Error('Member not found');
87+
}
88+
89+
return data.map(mapDBMemberToIMember);
90+
},
91+
92+
93+
5494
};

0 commit comments

Comments
 (0)