Skip to content

Commit 0fcaad1

Browse files
committed
Preformace Improvement + Code QOF Commits
1 parent 924696b commit 0fcaad1

File tree

14 files changed

+163
-268
lines changed

14 files changed

+163
-268
lines changed

website-ts/src/app/fantasy-football/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,17 @@ export default function FantasyFootball() {
6666
{
6767
id: 'rookie-talk',
6868
label: <><IconRookie size={18} className="flex-shrink-0" /> Rookie Talk</>,
69-
content: <RookieTalk />,
69+
content: () => <RookieTalk />,
7070
},
7171
{
7272
id: 'radar',
7373
label: <><IconRadar size={18} className="flex-shrink-0" /> Fantasy Radar</>,
74-
content: <FantasyRadar data={data} />,
74+
content: () => <FantasyRadar data={data} />,
7575
},
7676
{
7777
id: 'deep-dive',
7878
label: <><IconDeepDive size={18} className="flex-shrink-0" /> Deep Dive Tool</>,
79-
content: <DeepDive data={data} />,
79+
content: () => <DeepDive data={data} />,
8080
},
8181
]
8282

website-ts/src/app/news/[slug]/page.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@ import path from 'path'
44
import ArticleViewer from '@/components/news/ArticleViewer'
55
import { ArticlesIndex } from '@/types'
66

7-
// Generate static params for all articles
87
export async function generateStaticParams() {
8+
const slugs: { slug: string }[] = []
99
try {
1010
const indexPath = path.join(process.cwd(), 'public', 'json_data', 'articles_index.json')
1111
if (fs.existsSync(indexPath)) {
1212
const content = fs.readFileSync(indexPath, 'utf-8')
1313
const data: ArticlesIndex = JSON.parse(content)
14-
return data.articles.map((article) => ({
15-
slug: article.slug,
16-
}))
14+
slugs.push(...data.articles.map((article) => ({ slug: article.slug })))
1715
}
18-
return []
1916
} catch (error) {
2017
console.error('Error generating static params:', error)
21-
return []
2218
}
19+
// Next.js 14 with output:'export' treats an empty return as "missing
20+
// generateStaticParams" (it checks prerenderRoutes.length, not function
21+
// existence). A fallback slug keeps the build passing when 0 articles exist;
22+
// ArticleViewer already handles unknown slugs gracefully.
23+
if (slugs.length === 0) {
24+
slugs.push({ slug: '_' })
25+
}
26+
return slugs
2327
}
2428

2529
export default async function ArticlePage({ params }: { params: Promise<{ slug: string }> }) {

website-ts/src/components/Autocomplete.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useRef, useEffect } from 'react'
3+
import { useState, useRef, useEffect, useMemo } from 'react'
44
import { IconChevronDown } from '@/components/Icons'
55

66
interface Option {
@@ -37,12 +37,11 @@ export default function Autocomplete({
3737
// Find selected option label
3838
const selectedOption = options.find(o => o.value === value)
3939

40-
// Filter options based on input
41-
const filteredOptions = inputValue.trim() === ''
42-
? options
43-
: options.filter(o =>
44-
o.label.toLowerCase().includes(inputValue.toLowerCase())
45-
)
40+
const filteredOptions = useMemo(() => {
41+
if (inputValue.trim() === '') return options
42+
const query = inputValue.toLowerCase()
43+
return options.filter(o => o.label.toLowerCase().includes(query))
44+
}, [options, inputValue])
4645

4746
// Update input value when selection changes externally
4847
useEffect(() => {

website-ts/src/components/DataTable.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ interface DataTableProps<T> {
2525
columnGradients?: Record<string, ColumnGradientDirection>
2626
}
2727

28+
function getValueRaw<T>(row: T, key: string): any {
29+
const keys = key.split('.')
30+
let value: any = row
31+
for (const k of keys) value = value?.[k]
32+
return value
33+
}
34+
2835
export default function DataTable<T extends Record<string, any>>({
2936
data,
3037
columns,
@@ -49,13 +56,6 @@ export default function DataTable<T extends Record<string, any>>({
4956
return 0.1 + ((value - minVal) / (maxVal - minVal)) * 0.4
5057
}
5158

52-
function getValueRaw(row: T, key: string): any {
53-
const keys = key.split('.')
54-
let value: any = row
55-
for (const k of keys) value = value?.[k]
56-
return value
57-
}
58-
5959
const columnMinMax = React.useMemo(() => {
6060
if (!columnGradients || Object.keys(columnGradients).length === 0) return {}
6161
const result: Record<string, { min: number; max: number }> = {}
@@ -85,8 +85,6 @@ export default function DataTable<T extends Record<string, any>>({
8585
}
8686
}
8787

88-
const getValue = (row: T, key: string): any => getValueRaw(row, key)
89-
9088
return (
9189
<div
9290
className={`overflow-x-auto rounded-lg border border-[var(--border-color)] ${className}`}
@@ -137,7 +135,7 @@ export default function DataTable<T extends Record<string, any>>({
137135
>
138136
{columns.map(col => {
139137
const colKey = String(col.key)
140-
const rawValue = getValue(row, colKey)
138+
const rawValue = getValueRaw(row, colKey)
141139
const displayValue = col.format ? col.format(rawValue, row) : rawValue
142140
const cellClass = col.cellClass ? col.cellClass(rawValue, row) : ''
143141
const isTextOnlyClass = cellClass && !cellClass.includes('bg-')

website-ts/src/components/PlaybookBackground.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { useEffect, useState, useCallback } from 'react'
44

5-
// Unique ID counter
65
let routeIdCounter = 0
76

87
interface Route {
@@ -21,12 +20,16 @@ const MOBILE_ACTIVE_MAX = 5
2120
const DESKTOP_ACTIVE_MIN = 12
2221
const DESKTOP_ACTIVE_MAX = 16
2322

23+
function random(min: number, max: number) { return Math.random() * (max - min) + min }
24+
function randomInt(min: number, max: number) { return Math.floor(random(min, max)) }
25+
function safeX(val: number) { return Math.min(Math.max(val, 3), 97) }
26+
function safeY(val: number) { return Math.min(Math.max(val, 3), 94) }
27+
2428
export default function PlaybookBackground() {
2529
const [routes, setRoutes] = useState<Route[]>([])
2630
const [isMobile, setIsMobile] = useState(false)
2731
const [hasMeasured, setHasMeasured] = useState(false)
2832

29-
// Detect mobile viewport
3033
useEffect(() => {
3134
const check = () => {
3235
if (typeof window !== 'undefined') {
@@ -39,13 +42,6 @@ export default function PlaybookBackground() {
3942
return () => window.removeEventListener('resize', check)
4043
}, [])
4144

42-
// --- HELPERS ---
43-
const random = useCallback((min: number, max: number) => Math.random() * (max - min) + min, [])
44-
const randomInt = useCallback((min: number, max: number) => Math.floor(random(min, max)), [random])
45-
const safeX = useCallback((val: number) => Math.min(Math.max(val, 3), 97), [])
46-
const safeY = useCallback((val: number) => Math.min(Math.max(val, 3), 94), [])
47-
48-
// --- CREATE ROUTE ---
4945
const createRoute = useCallback((slotIndex: number, neighbors: (number | undefined)[], initialDelay: boolean, slotCount: number): Route => {
5046
const slotWidth = 100 / slotCount
5147
const slotBase = (slotIndex * slotWidth) + (slotWidth / 2)
@@ -146,7 +142,7 @@ export default function PlaybookBackground() {
146142
type,
147143
slotIndex
148144
}
149-
}, [random, randomInt, safeX, safeY])
145+
}, [])
150146

151147
// --- INITIAL MOUNT ---
152148
useEffect(() => {

website-ts/src/components/PlayerHeadshot.tsx

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client'
22

3-
import { useState, useEffect, useRef } from 'react'
3+
import { useState } from 'react'
44
import Image from 'next/image'
5-
import { getHeadshotPath, didHeadshotFail, getFallbackHeadshot } from '@/utils/dataLoader'
5+
import { getHeadshotPath } from '@/utils/dataLoader'
66
import { IconDevProfile } from '@/components/Icons'
77

88
interface PlayerHeadshotProps {
@@ -22,77 +22,32 @@ export default function PlayerHeadshot({
2222
}: PlayerHeadshotProps) {
2323
const [hasError, setHasError] = useState(false)
2424
const [isLoaded, setIsLoaded] = useState(false)
25-
const [isInView, setIsInView] = useState(priority)
26-
const imgRef = useRef<HTMLDivElement>(null)
2725

2826
const headshotPath = getHeadshotPath(playerName, position)
2927

30-
// Check if already known to have failed
31-
useEffect(() => {
32-
if (didHeadshotFail(headshotPath)) {
33-
setHasError(true)
34-
}
35-
}, [headshotPath])
36-
37-
// Intersection observer for lazy loading
38-
useEffect(() => {
39-
if (priority || isInView) return
40-
41-
const observer = new IntersectionObserver(
42-
(entries) => {
43-
if (entries[0].isIntersecting) {
44-
setIsInView(true)
45-
observer.disconnect()
46-
}
47-
},
48-
{
49-
rootMargin: '100px', // Start loading 100px before in view
50-
}
51-
)
52-
53-
if (imgRef.current) {
54-
observer.observe(imgRef.current)
55-
}
56-
57-
return () => observer.disconnect()
58-
}, [priority, isInView])
59-
60-
const handleError = () => {
61-
setHasError(true)
62-
}
63-
64-
const handleLoad = () => {
65-
setIsLoaded(true)
66-
}
67-
6828
return (
6929
<div
70-
ref={imgRef}
7130
className={`relative overflow-hidden rounded-full bg-[var(--bg-secondary)] ${className}`}
7231
style={{ width: size, height: size }}
7332
>
74-
{/* Skeleton loading state */}
7533
{!isLoaded && !hasError && (
7634
<div className="absolute inset-0 skeleton" />
7735
)}
7836

79-
{/* Actual image - only render when in view */}
80-
{isInView && !hasError && (
37+
{!hasError && (
8138
<Image
8239
src={headshotPath}
8340
alt={`${playerName} headshot`}
8441
width={size}
8542
height={size}
86-
className={`object-cover transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'
87-
}`}
88-
onError={handleError}
89-
onLoad={handleLoad}
43+
className={`object-cover transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
44+
onError={() => setHasError(true)}
45+
onLoad={() => setIsLoaded(true)}
9046
loading={priority ? 'eager' : 'lazy'}
9147
unoptimized
9248
/>
9349
)}
9450

95-
{/* Fallback icon on error */}
9651
{hasError && (
9752
<div className="absolute inset-0 flex items-center justify-center bg-[var(--bg-secondary)] text-[var(--text-muted)]">
9853
<IconDevProfile size={size * 0.6} />

website-ts/src/components/Tabs.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { useState, ReactNode } from 'react'
55
interface Tab {
66
id: string
77
label: ReactNode
8-
content: ReactNode
9-
description?: string // Optional description for the tab
8+
content: ReactNode | (() => ReactNode)
9+
description?: string
1010
}
1111

1212
interface TabsProps {
@@ -17,7 +17,8 @@ interface TabsProps {
1717
export default function Tabs({ tabs, defaultTab }: TabsProps) {
1818
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id)
1919
const activeTabData = tabs.find(t => t.id === activeTab)
20-
const activeContent = activeTabData?.content
20+
const rawContent = activeTabData?.content
21+
const activeContent = typeof rawContent === 'function' ? rawContent() : rawContent
2122
const activeDescription = activeTabData?.description
2223

2324
return (

website-ts/src/components/tabs/FantasyHome.tsx

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,16 @@
11
'use client'
22

3-
import { useMemo, useState, useEffect } from 'react'
3+
import { useMemo } from 'react'
44
import Metric from '@/components/Metric'
55
import Divider from '@/components/Divider'
66
import InfoBox from '@/components/InfoBox'
7-
import { PlayerData } from '@/types'
7+
import { PlayerData, DraftPick } from '@/types'
8+
import { useMockDraft } from '@/hooks/useMockDraft'
89

910
interface FantasyHomeProps {
1011
data: PlayerData[]
1112
}
1213

13-
interface DraftPick {
14-
pick: number
15-
round: number
16-
team: string
17-
player_name: string
18-
position: string
19-
college: string
20-
height: string
21-
weight: number
22-
stats: Record<string, number>
23-
}
24-
25-
interface MockDraftData {
26-
picks: DraftPick[]
27-
}
28-
2914
// Team name mappings
3015
const TEAM_NAMES: Record<string, string> = {
3116
'LV': 'Las Vegas Raiders',
@@ -90,23 +75,7 @@ const TOP_PROSPECT_BLURBS: Record<string, { description: string; highlights: str
9075
const FEATURED_PROSPECT_PICKS = [1, 8, 4, 16, 21] // Mendoza, Love, Tate, Lemon, Simpson
9176

9277
export default function FantasyHome({ data }: FantasyHomeProps) {
93-
const [mockDraft, setMockDraft] = useState<MockDraftData | null>(null)
94-
95-
// Load mock draft data
96-
useEffect(() => {
97-
const loadMockDraft = async () => {
98-
try {
99-
const response = await fetch('/json_data/mock_draft.json')
100-
if (response.ok) {
101-
const data = await response.json()
102-
setMockDraft(data)
103-
}
104-
} catch (error) {
105-
console.error('Error loading mock draft:', error)
106-
}
107-
}
108-
loadMockDraft()
109-
}, [])
78+
const mockDraft = useMockDraft()
11079

11180
// Find the latest year
11281
const latestYear = useMemo(() => {

website-ts/src/components/tabs/FantasyRadar.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IconStar, IconWarning, IconTrendUp, IconFire, IconCrystalBall } from '@
99
import { PlayerData, BoomBustStats } from '@/types'
1010
import { calculateBoomBust } from '@/utils/calculations'
1111
import { getAvailableSeasons } from '@/utils/dataLoader'
12+
import { getPositionBadgeClass } from '@/utils/teamColors'
1213

1314
interface FantasyRadarProps {
1415
data: PlayerData[]
@@ -295,17 +296,6 @@ export default function FantasyRadar({ data }: FantasyRadarProps) {
295296
},
296297
]
297298

298-
const getPositionBadgeClass = (position: string) => {
299-
const p = (position ?? '').trim().toUpperCase()
300-
switch (p) {
301-
case 'QB': return 'bg-red-500/20 text-red-400'
302-
case 'RB': return 'bg-green-500/20 text-green-400'
303-
case 'WR': return 'bg-blue-500/20 text-blue-400'
304-
case 'TE': return 'bg-orange-500/20 text-orange-400'
305-
default: return 'bg-gray-500/20 text-gray-400'
306-
}
307-
}
308-
309299
if (filteredData.length === 0) {
310300
return (
311301
<div>

0 commit comments

Comments
 (0)