Skip to content

Commit 1a62a76

Browse files
authored
Merge pull request #357 from bruin-tennis-consulting/development
Development
2 parents b11817a + 84f6171 commit 1a62a76

29 files changed

Lines changed: 5240 additions & 694 deletions

app/DataProvider.js

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
doc,
1313
updateDoc,
1414
addDoc,
15+
getDoc,
1516
query,
1617
where
1718
} from 'firebase/firestore'
@@ -20,6 +21,7 @@ import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'
2021
import { db, storage } from '@/app/services/initializeFirebase.js'
2122
import { useAuth } from '@/app/AuthWrapper.js'
2223
import getTeams from '@/app/services/getTeams.js'
24+
import { getLogoFromCache, setLogoInCache } from '@/app/services/logoCache'
2325

2426
const DataContext = createContext()
2527

@@ -39,7 +41,7 @@ export const DataProvider = ({ children }) => {
3941

4042
const { userProfile } = useAuth()
4143

42-
// Optmized fetchMatches version
44+
// Optimized fetchMatches version with query filtering
4345
const fetchMatches = useCallback(async () => {
4446
if (!userProfile?.collections?.length) return
4547

@@ -74,6 +76,21 @@ export const DataProvider = ({ children }) => {
7476
}
7577
}, [userProfile])
7678

79+
// Function to fetch detailed match data including points
80+
const fetchMatchDetails = useCallback(async (matchId, collectionName) => {
81+
try {
82+
const matchRef = doc(db, collectionName, matchId)
83+
const matchDoc = await getDoc(matchRef)
84+
if (matchDoc.exists()) {
85+
return matchDoc.data()
86+
}
87+
return null
88+
} catch (error) {
89+
console.error('Error fetching match details:', error)
90+
return null
91+
}
92+
}, [])
93+
7794
const updateMatch = useCallback(
7895
async (matchId, updatedData) => {
7996
try {
@@ -143,34 +160,25 @@ export const DataProvider = ({ children }) => {
143160
)
144161

145162
const fetchLogos = useCallback(async () => {
146-
// Cache expiry time, currently 24 hours
147-
const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000
148-
const storedLogos = localStorage.getItem('teamLogos')
149-
const storedTimeStamp = localStorage.getItem('teamLogosTimestamp')
150-
151-
if (storedLogos && storedTimeStamp) {
152-
// check if cache expired
153-
const cacheAge = Date.now() - parseInt(storedTimeStamp, 10)
154-
if (cacheAge < CACHE_EXPIRY_MS) {
155-
setLogos(JSON.parse(storedLogos))
156-
setLogosLoading(false)
157-
return
158-
}
159-
}
160-
161163
setLogosLoading(true)
162164
setLogosError(null)
163165

164166
try {
165167
const teams = await getTeams()
166168
const logosMap = teams.reduce((acc, team) => {
167-
acc[team.name] = team.logoUrl
169+
// Check cache first
170+
const cachedLogo = getLogoFromCache(team.name)
171+
if (cachedLogo) {
172+
acc[team.name] = cachedLogo
173+
} else {
174+
acc[team.name] = team.logoUrl
175+
// Cache the logo
176+
setLogoInCache(team.name, team.logoUrl)
177+
}
168178
return acc
169179
}, {})
170180

171181
setLogos(logosMap)
172-
localStorage.setItem('teamLogos', JSON.stringify(logosMap))
173-
localStorage.setItem('teamLogosTimestamp', Date.now().toString())
174182
} catch (err) {
175183
setLogosError(err)
176184
console.error('Error fetching team logos:', err)
@@ -192,6 +200,7 @@ export const DataProvider = ({ children }) => {
192200
loading: loading || logosLoading,
193201
error: error || logosError,
194202
refresh: fetchMatches,
203+
fetchMatchDetails,
195204
updateMatch,
196205
createMatch
197206
}}
@@ -208,9 +217,26 @@ export const useData = () => {
208217
throw new Error('useData must be used within a MatchDataProvider')
209218
}
210219

211-
const { matches, logos, loading, error, refresh, updateMatch, createMatch } =
212-
context
220+
const {
221+
matches,
222+
logos,
223+
loading,
224+
error,
225+
refresh,
226+
fetchMatchDetails,
227+
updateMatch,
228+
createMatch
229+
} = context
213230

214231
// Optionally keep `refresh` available for manual use in components
215-
return { matches, logos, loading, error, refresh, updateMatch, createMatch }
232+
return {
233+
matches,
234+
logos,
235+
loading,
236+
error,
237+
refresh,
238+
fetchMatchDetails,
239+
updateMatch,
240+
createMatch
241+
}
216242
}

app/components/Dashboard.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { searchableProperties } from '@/app/services/searchableProperties.js'
1717
import SearchIcon from '@/public/search'
1818

1919
const cleanTeamName = (teamName) => {
20-
return teamName.replace(/\s*\([MmWw]\)\s*$/, '').trim()
20+
return teamName ? teamName.replace(/\s*\([MmWw]\)\s*$/, '').trim() : teamName
2121
}
2222

2323
const getUniqueMatches = (matches, cleanTeamName) => {
@@ -55,7 +55,7 @@ const CarouselItem = React.memo(({ match, isSelected, onClick, logo }) => {
5555
onClick={() => onClick(matchKey)}
5656
>
5757
<Image
58-
src={imageSrc}
58+
src={imageSrc || '/images/default-logo.svg'}
5959
loading="lazy"
6060
alt="Team Logo"
6161
width={50}
@@ -64,6 +64,13 @@ const CarouselItem = React.memo(({ match, isSelected, onClick, logo }) => {
6464
// Add blur placeholder for faster perceived loading
6565
placeholder="blur"
6666
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
67+
onError={(e) => {
68+
if (e.target.src !== '/images/default-logo.svg') {
69+
e.target.src = '/images/default-logo.svg'
70+
} else {
71+
e.target.style.display = 'none'
72+
}
73+
}}
6774
/>
6875
<span className={styles.matchDate}>{match.matchDate}</span>
6976
</div>
@@ -401,7 +408,9 @@ const Dashboard = () => {
401408

402409
const matchName = matchKey.split('#')[1]
403410
const displayName =
404-
matchName === '_' ? matchName : cleanTeamName(matchName)
411+
matchName === '_'
412+
? matchName
413+
: matchName.replace(/\s+\([MW]\)$/, '')
405414

406415
const parseLocalDate = (dateString) => {
407416
const [year, month, day] = dateString.split('-').map(Number)

app/components/DashboardTile.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Image from 'next/image'
33

44
import styles from '@/app/styles/DashboardTile.module.css'
55
import { useData } from '@/app/DataProvider'
6+
import { getLogoFromCache, setLogoInCache } from '@/app/services/logoCache'
67

78
const DashboardTile = ({
89
clientTeam,
@@ -53,9 +54,27 @@ const DashboardTile = ({
5354
}
5455

5556
useEffect(() => {
56-
setClientLogo(logos[clientTeam])
57-
setOpponentLogo(logos[opponentTeam])
57+
// Check cache first
58+
const cachedClientLogo = getLogoFromCache(clientTeam)
59+
const cachedOpponentLogo = getLogoFromCache(opponentTeam)
60+
61+
if (cachedClientLogo && cachedOpponentLogo) {
62+
setClientLogo(cachedClientLogo)
63+
setOpponentLogo(cachedOpponentLogo)
64+
return
65+
}
66+
67+
// If not in cache, use the logos from DataProvider
68+
if (logos[clientTeam]) {
69+
setClientLogo(logos[clientTeam])
70+
setLogoInCache(clientTeam, logos[clientTeam])
71+
}
72+
if (logos[opponentTeam]) {
73+
setOpponentLogo(logos[opponentTeam])
74+
setLogoInCache(opponentTeam, logos[opponentTeam])
75+
}
5876
}, [clientTeam, opponentTeam, logos])
77+
5978
// Render function for scores
6079
const renderScore = (score, index, isPlayer1, tieScores) => {
6180
const lastSetIndex =
@@ -129,10 +148,17 @@ const DashboardTile = ({
129148
<div className={styles.playerSchoolImgcontainerhome}>
130149
{clientLogo ? (
131150
<Image
132-
src={clientLogo}
151+
src={clientLogo || '/images/default-logo.svg'}
133152
alt={`${clientTeam} logo`}
134153
width={100}
135154
height={100}
155+
onError={(e) => {
156+
if (e.target.src !== '/images/default-logo.svg') {
157+
e.target.src = '/images/default-logo.svg'
158+
} else {
159+
e.target.style.display = 'none'
160+
}
161+
}}
136162
/>
137163
) : (
138164
<p>Logo not available</p>
@@ -154,10 +180,17 @@ const DashboardTile = ({
154180
<div className={styles.playerSchoolImgcontainer}>
155181
{opponentLogo ? (
156182
<Image
157-
src={opponentLogo}
183+
src={opponentLogo || '/images/default-logo.svg'}
158184
alt={`${opponentTeam} logo`}
159185
width={100}
160186
height={100}
187+
onError={(e) => {
188+
if (e.target.src !== '/images/default-logo.svg') {
189+
e.target.src = '/images/default-logo.svg'
190+
} else {
191+
e.target.style.display = 'none'
192+
}
193+
}}
161194
/>
162195
) : (
163196
<p>Logo not available</p>

app/components/ExtendedList.js

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import React, { useEffect, useState } from 'react'
1+
import React from 'react'
22
import Image from 'next/image'
3-
43
import styles from '@/app/styles/ExtendedList.module.css'
5-
import getTeams from '@/app/services/getTeams.js'
6-
4+
import { useTeamLogos } from '@/app/hooks/useTeamLogos'
75
import Winner from '@/public/Winner.js'
86
import Error from '@/public/Error.js'
97
import DoubleFault from '@/public/DoubleFault'
@@ -16,29 +14,15 @@ const ExtendedList = ({
1614
onPointSelect,
1715
iframe
1816
}) => {
19-
const [clientLogo, setClientLogo] = useState('')
20-
const [opponentLogo, setOpponentLogo] = useState('')
17+
const { clientLogo, opponentLogo, loading } = useTeamLogos(
18+
clientTeam,
19+
opponentTeam
20+
)
2121

22-
useEffect(() => {
23-
const fetchLogos = async () => {
24-
try {
25-
const allTeams = await getTeams()
26-
console.log(allTeams)
27-
const clientLogoURL = allTeams.find(
28-
(team) => team.name === clientTeam
29-
).logoUrl
30-
const opponentLogoURL = allTeams.find(
31-
(team) => team.name === opponentTeam
32-
).logoUrl
33-
setClientLogo(clientLogoURL)
34-
setOpponentLogo(opponentLogoURL)
35-
} catch (error) {
36-
console.error('Error fetching data:', error)
37-
}
38-
}
22+
if (loading) {
23+
return <div>Loading logos...</div>
24+
}
3925

40-
fetchLogos()
41-
})
4226
const keys = [
4327
'',
4428
'serverName',
@@ -62,7 +46,6 @@ const ExtendedList = ({
6246
]
6347

6448
const Scroll = (point) => {
65-
// useref
6649
onPointSelect(point.Position)
6750
if (iframe.current) {
6851
iframe.current.scrollIntoView({ behavior: 'smooth' })
@@ -87,49 +70,68 @@ const ExtendedList = ({
8770
{keys.map((key, cellIndex) => (
8871
<td className={styles.TD} key={cellIndex}>
8972
{cellIndex === 0 ? (
90-
<div className={styles.imageWrapper}>
73+
<div className={styles.playerSchoolImg}>
9174
<Image
9275
src={
9376
item.player1Name === item.serverName
94-
? clientLogo
95-
: opponentLogo
77+
? clientLogo || '/images/default-logo.svg'
78+
: opponentLogo || '/images/default-logo.svg'
9679
}
9780
alt={
9881
item.player1Name === item.serverName
9982
? `${clientTeam} logo`
10083
: `${opponentTeam} logo`
10184
}
102-
layout="fill"
103-
objectFit="contain"
10485
className={styles.IMG}
86+
width={30}
87+
height={30}
88+
onError={(e) => {
89+
if (e.target.src !== '/images/default-logo.svg') {
90+
e.target.src = '/images/default-logo.svg'
91+
} else {
92+
e.target.style.display = 'none'
93+
}
94+
}}
10595
/>
10696
</div>
107-
) : (
108-
<div style={{ display: 'flex', alignItems: 'center' }}>
109-
{cellIndex === keys.length - 2 ? (
110-
<>
111-
{item.lastShotResult === 'Winner' && <Winner />}
112-
{item.lastShotResult === 'Error' && <Error />}
113-
{item.lastShotResult === 'DoubleFault' && (
114-
<DoubleFault />
115-
)}
116-
<span style={{ marginLeft: '4px' }}>
117-
{item.lastShotResult}
118-
</span>
119-
</>
97+
) : cellIndex === 1 ? (
98+
<div className={styles.serverIcon}>
99+
{item.serverName === item.player1Name ? (
100+
<PlayButton />
120101
) : (
121-
item[key]
102+
<PlayButton />
122103
)}
123104
</div>
105+
) : cellIndex === 5 ? (
106+
<div className={styles.winnerIcon}>
107+
{item.pointWonBy === item.player1Name ? (
108+
<Winner />
109+
) : (
110+
<Winner />
111+
)}
112+
</div>
113+
) : cellIndex === 6 ? (
114+
<div className={styles.errorIcon}>
115+
{item.lastShotResult === 'Error' ? (
116+
<Error />
117+
) : item.lastShotResult === 'Double Fault' ? (
118+
<DoubleFault />
119+
) : null}
120+
</div>
121+
) : cellIndex === 7 ? (
122+
<div className={styles.rallyCount}>{item.rallyCount}</div>
123+
) : cellIndex === 8 ? (
124+
<button
125+
className={styles.scrollButton}
126+
onClick={() => Scroll(item)}
127+
>
128+
Go to Point
129+
</button>
130+
) : (
131+
item[key]
124132
)}
125133
</td>
126134
))}
127-
128-
<td className={styles.TD2}>
129-
<button className={styles.button} onClick={() => Scroll(item)}>
130-
<PlayButton />
131-
</button>
132-
</td>
133135
</tr>
134136
))}
135137
</tbody>

0 commit comments

Comments
 (0)