Skip to content

Commit 18884d2

Browse files
committed
added buttons to sort season
1 parent 79e4fca commit 18884d2

3 files changed

Lines changed: 311 additions & 614 deletions

File tree

app/components/Dashboard.js

Lines changed: 137 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,53 @@ const formatMatches = (matches) => {
3939
.sort((a, b) => new Date(b.matchDate) - new Date(a.matchDate))
4040
}
4141

42+
// Helper function to determine which season a match belongs to
43+
const getMatchSeason = (matchDate) => {
44+
const date = new Date(matchDate)
45+
const month = date.getMonth() + 1 // 1-12
46+
const year = date.getFullYear()
47+
48+
// College tennis seasons:
49+
// Fall (Aug-Nov): part of next year's season (e.g., Aug 2024 = 2024-2025)
50+
// Spring (Jan-May): part of current year's season (e.g., Jan 2025 = 2024-2025)
51+
52+
if (month >= 8) {
53+
// Aug-Dec: part of current-next season
54+
return `${year}-${year + 1}`
55+
} else if (month <= 5) {
56+
// Jan-May: part of previous-current season
57+
return `${year - 1}-${year}`
58+
}
59+
// June-July: off-season, still count as previous-current season
60+
return `${year - 1}-${year}`
61+
}
62+
63+
// Get current season based on today's date
64+
const getCurrentSeason = () => {
65+
return getMatchSeason(new Date().toISOString().split('T')[0])
66+
}
67+
68+
// Get all unique seasons from matches
69+
const getAvailableSeasons = (matches) => {
70+
const seasons = new Set()
71+
const currentYear = new Date().getFullYear()
72+
73+
matches.forEach((match) => {
74+
const season = getMatchSeason(match.matchDate)
75+
76+
// Only include seasons from the last 10 years
77+
const seasonStartYear = parseInt(season.split('-')[0])
78+
if (
79+
seasonStartYear >= currentYear - 30 &&
80+
seasonStartYear <= currentYear + 1
81+
) {
82+
seasons.add(season)
83+
}
84+
})
85+
86+
return Array.from(seasons).sort().reverse() // Most recent first
87+
}
88+
4289
// Memoized CarouselItem with fixed dimensions but original logo container
4390
const CarouselItem = React.memo(({ match, isSelected, onClick, logo }) => {
4491
const matchKey = match.matchDetails.duel
@@ -116,10 +163,39 @@ const SearchBox = React.memo(({ searchTerm, onSearch, onClear }) => {
116163

117164
SearchBox.displayName = 'SearchBox'
118165

166+
// Season Filter Component
167+
const SeasonFilter = React.memo(
168+
({ availableSeasons, selectedSeason, onSeasonChange }) => {
169+
return (
170+
<div className={styles.seasonFilterContainer}>
171+
<div className={styles.seasonButtons}>
172+
{availableSeasons.map((season) => (
173+
<button
174+
key={season}
175+
className={`${styles.seasonButton} ${selectedSeason === season ? styles.activeSeason : ''}`}
176+
onClick={() => onSeasonChange(season)}
177+
>
178+
{season}
179+
</button>
180+
))}
181+
<button
182+
className={`${styles.seasonButton} ${selectedSeason === 'all' ? styles.activeSeason : ''}`}
183+
onClick={() => onSeasonChange('all')}
184+
>
185+
All Seasons
186+
</button>
187+
</div>
188+
</div>
189+
)
190+
}
191+
)
192+
193+
SeasonFilter.displayName = 'SeasonFilter'
194+
119195
// MatchSection with placeholder heights
120196
const MatchSection = React.memo(
121-
({ matchKey, formattedMatches, onTileClick }) => {
122-
const singlesMatches = formattedMatches.filter(
197+
({ matchKey, seasonFilteredMatches, onTileClick }) => {
198+
const singlesMatches = seasonFilteredMatches.filter(
123199
(match) =>
124200
match.singles &&
125201
((match.matchDetails.duel &&
@@ -129,7 +205,7 @@ const MatchSection = React.memo(
129205
matchKey === `_#${match.matchDetails.event}`))
130206
)
131207

132-
const doublesMatches = formattedMatches.filter(
208+
const doublesMatches = seasonFilteredMatches.filter(
133209
(match) =>
134210
!match.singles &&
135211
((match.matchDetails.duel &&
@@ -142,7 +218,7 @@ const MatchSection = React.memo(
142218
const [matchName] = matchKey.split('#')
143219
const displayName =
144220
matchName === '_'
145-
? formattedMatches.find(
221+
? seasonFilteredMatches.find(
146222
(match) =>
147223
!match.matchDetails.duel && match.matchDetails.event === matchName
148224
)?.matchDetails.event || matchName
@@ -231,6 +307,7 @@ const Dashboard = () => {
231307
const [selectedMatchSets, setSelectedMatchSets] = useState([])
232308
const [isMobile, setIsMobile] = useState(false)
233309
const [isLoaded, setIsLoaded] = useState(false)
310+
const [selectedSeason, setSelectedSeason] = useState(getCurrentSeason())
234311

235312
const [searchIndex, setSearchIndex] = useState(null)
236313
// Calculate formatted matches once
@@ -243,11 +320,33 @@ const Dashboard = () => {
243320
return formatted
244321
}, [matches, isLoaded])
245322

246-
const uniqueMatches = useMemo(
247-
() => getUniqueMatches(formattedMatches, cleanTeamName),
248-
[formattedMatches]
323+
// Filter matches by selected season
324+
const seasonFilteredMatches = useMemo(() => {
325+
if (selectedSeason === 'all') {
326+
return formattedMatches
327+
}
328+
return formattedMatches.filter(
329+
(match) => getMatchSeason(match.matchDate) === selectedSeason
330+
)
331+
}, [formattedMatches, selectedSeason])
332+
333+
const handleSeasonChange = useCallback(
334+
(season) => {
335+
setSelectedSeason(season)
336+
setSelectedMatchSets([]) // Clear carousel selections when season changes
337+
},
338+
[setSelectedSeason, setSelectedMatchSets]
249339
)
250340

341+
// Get available seasons from all matches
342+
const availableSeasons = useMemo(() => {
343+
return getAvailableSeasons(formattedMatches)
344+
}, [formattedMatches])
345+
346+
const uniqueMatches = useMemo(() => {
347+
return getUniqueMatches(seasonFilteredMatches, cleanTeamName)
348+
}, [])
349+
251350
// Mobile detection useEffect
252351
useEffect(() => {
253352
const checkIfMobile = () => setIsMobile(window.innerWidth < 400)
@@ -259,11 +358,11 @@ const Dashboard = () => {
259358
return () => resizeObserver.disconnect()
260359
}, [])
261360

262-
// Create search index after component mounts and formattedMatches is available
361+
// Create search index after component mounts and seasonFilteredMatches is available
263362
useEffect(() => {
264-
if (formattedMatches.length && !searchIndex) {
363+
if (seasonFilteredMatches.length && !searchIndex) {
265364
const timer = setTimeout(() => {
266-
const fuse = new Fuse(formattedMatches, {
365+
const fuse = new Fuse(seasonFilteredMatches, {
267366
keys: searchableProperties,
268367
threshold: 0.4
269368
})
@@ -272,7 +371,17 @@ const Dashboard = () => {
272371

273372
return () => clearTimeout(timer)
274373
}
275-
}, [formattedMatches, searchIndex]) // Now formattedMatches is defined before this useEffect
374+
}, [seasonFilteredMatches, searchIndex, searchableProperties])
375+
376+
useEffect(() => {
377+
if (seasonFilteredMatches.length) {
378+
const fuse = new Fuse(seasonFilteredMatches, {
379+
keys: searchableProperties,
380+
threshold: 0.4
381+
})
382+
setSearchIndex(fuse)
383+
}
384+
}, [selectedSeason, searchableProperties])
276385

277386
// Filtered match sets calculation using the searchIndex from state
278387
const filteredMatchSets = useMemo(() => {
@@ -304,14 +413,14 @@ const Dashboard = () => {
304413
if (selectedMatchSets.length > 0) return selectedMatchSets
305414
return [
306415
...new Set(
307-
formattedMatches.map((match) => {
416+
seasonFilteredMatches.map((match) => {
308417
return match.matchDetails.duel
309418
? `${match.matchDate}#${match.teams.opponentTeam}`
310419
: `_#${match.matchDetails.event}`
311420
})
312421
)
313422
]
314-
}, [searchTerm, filteredMatchSets, selectedMatchSets, formattedMatches])
423+
}, [searchTerm, filteredMatchSets, selectedMatchSets])
315424

316425
const handleTileClick = useCallback(
317426
(videoId) => {
@@ -339,16 +448,23 @@ const Dashboard = () => {
339448
<header className={styles.header}>
340449
<div className={styles.headerContent}>
341450
<h2>Dashboard</h2>
342-
<SearchBox
343-
searchTerm={searchTerm}
344-
onSearch={handleSearch}
345-
onClear={handleClearSearch}
346-
/>
451+
<div className={styles.headerControls}>
452+
<SearchBox
453+
searchTerm={searchTerm}
454+
onSearch={handleSearch}
455+
onClear={handleClearSearch}
456+
/>
457+
<SeasonFilter
458+
availableSeasons={availableSeasons}
459+
selectedSeason={selectedSeason}
460+
onSeasonChange={handleSeasonChange}
461+
/>
462+
</div>
347463
</div>
348464
</header>
349465

350466
<div className={styles.carousel}>
351-
{!formattedMatches.length
467+
{!seasonFilteredMatches.length
352468
? Array(10)
353469
.fill(0)
354470
.map((_, i) => (
@@ -386,7 +502,7 @@ const Dashboard = () => {
386502
<div className={styles.noMatches}>No matches found</div>
387503
) : (
388504
displayMatchSets.map((matchKey, index) => {
389-
const singlesMatches = formattedMatches.filter(
505+
const singlesMatches = seasonFilteredMatches.filter(
390506
(match) =>
391507
match.singles &&
392508
((match.matchDetails.duel &&
@@ -396,7 +512,7 @@ const Dashboard = () => {
396512
matchKey === `_#${match.matchDetails.event}`))
397513
)
398514

399-
const doublesMatches = formattedMatches.filter(
515+
const doublesMatches = seasonFilteredMatches.filter(
400516
(match) =>
401517
!match.singles &&
402518
((match.matchDetails.duel &&

app/styles/Dashboard.module.css

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,71 @@
390390
opacity: 0.6;
391391
}
392392
}
393+
394+
/* Header Controls - wraps search and season filter */
395+
.headerControls {
396+
display: flex;
397+
gap: 20px;
398+
align-items: center;
399+
flex-wrap: wrap;
400+
}
401+
402+
/* Season Filter Container */
403+
.seasonFilterContainer {
404+
display: flex;
405+
align-items: center;
406+
gap: 12px;
407+
}
408+
409+
.seasonButtons {
410+
display: flex;
411+
gap: 8px;
412+
flex-wrap: wrap;
413+
}
414+
415+
.seasonButton {
416+
padding: 8px 16px;
417+
border: 2px solid #2774ae;
418+
background-color: transparent;
419+
color: #2774ae;
420+
border-radius: 6px;
421+
cursor: pointer;
422+
font-size: 14px;
423+
font-weight: 600;
424+
transition: all 0.2s ease;
425+
white-space: nowrap;
426+
}
427+
428+
.seasonButton:hover {
429+
background-color: rgba(39, 116, 174, 0.1);
430+
}
431+
432+
.seasonButton.activeSeason {
433+
background-color: #2774ae;
434+
color: white;
435+
}
436+
437+
/* Mobile responsiveness */
438+
@media (max-width: 768px) {
439+
.headerControls {
440+
flex-direction: column;
441+
align-items: stretch;
442+
width: 100%;
443+
}
444+
445+
.seasonFilterContainer {
446+
width: 100%;
447+
}
448+
449+
.seasonButtons {
450+
width: 100%;
451+
justify-content: center;
452+
}
453+
454+
.seasonButton {
455+
flex: 1;
456+
min-width: 80px;
457+
font-size: 12px;
458+
padding: 6px 10px;
459+
}
460+
}

0 commit comments

Comments
 (0)