@@ -214,7 +329,7 @@ definePageMeta({
middleware: ['auth'], // Require authentication
})
-const activeTab = ref<'active' | 'completed'>('active')
+const activeTab = ref<'schedule' | 'active' | 'completed'>('schedule')
const config = useRuntimeConfig()
const authStore = useAuthStore()
const router = useRouter()
@@ -224,6 +339,49 @@ const loading = ref(true)
const error = ref(null)
const isCreatingQuickGame = ref(false)
+// Schedule composable
+const schedule = useSchedule()
+
+// Group schedule games by matchup (same away_team + home_team)
+// Each group contains games sorted by game_id ascending
+interface ScheduleGroup {
+ key: string
+ awayTeam: { id: number; abbrev: string; sname: string }
+ homeTeam: { id: number; abbrev: string; sname: string }
+ games: typeof schedule.games.value
+}
+
+const groupedScheduleGames = computed(() => {
+ const gamesArray = schedule.games.value
+ if (!gamesArray || gamesArray.length === 0) return []
+
+ // Group by matchup key (away_team_id-home_team_id)
+ const groups = new Map()
+
+ for (const game of gamesArray) {
+ const key = `${game.away_team.id}-${game.home_team.id}`
+
+ if (!groups.has(key)) {
+ groups.set(key, {
+ key,
+ awayTeam: game.away_team,
+ homeTeam: game.home_team,
+ games: [],
+ })
+ }
+
+ groups.get(key)!.games.push(game)
+ }
+
+ // Sort games within each group by game_id ascending
+ for (const group of groups.values()) {
+ group.games.sort((a, b) => a.id - b.id)
+ }
+
+ // Convert to array and sort groups by first game's id
+ return Array.from(groups.values()).sort((a, b) => a.games[0].id - b.games[0].id)
+})
+
// Quick-create a demo game with pre-configured lineups
async function handleQuickCreate() {
try {
@@ -252,6 +410,48 @@ async function handleQuickCreate() {
}
}
+// Create a game from a scheduled matchup
+async function handlePlayScheduledGame(homeTeamId: number, awayTeamId: number) {
+ try {
+ isCreatingQuickGame.value = true
+ error.value = null
+
+ console.log(`[Games Page] Creating game: ${awayTeamId} @ ${homeTeamId}`)
+
+ const response = await $fetch<{ game_id: string; message: string; status: string }>(
+ `${config.public.apiUrl}/api/games/quick-create`,
+ {
+ method: 'POST',
+ credentials: 'include',
+ body: {
+ home_team_id: homeTeamId,
+ away_team_id: awayTeamId,
+ },
+ }
+ )
+
+ console.log('[Games Page] Created game from schedule:', response)
+
+ // Redirect to game page
+ router.push(`/games/${response.game_id}`)
+ } catch (err: any) {
+ console.error('[Games Page] Failed to create game from schedule:', err)
+ error.value = err.data?.detail || err.message || 'Failed to create game'
+ } finally {
+ isCreatingQuickGame.value = false
+ }
+}
+
+// Handle tab change
+function handleTabChange(tab: 'schedule' | 'active' | 'completed') {
+ activeTab.value = tab
+
+ // Initialize schedule data when switching to schedule tab
+ if (tab === 'schedule' && !schedule.initialized.value) {
+ schedule.initialize()
+ }
+}
+
// Fetch games - uses internal URL for SSR, public URL for client
const apiUrl = useApiUrl()
const { data: games, pending, error: fetchError, refresh } = await useAsyncData(
@@ -291,9 +491,14 @@ const completedGames = computed(() => {
return games.value?.filter(g => g.status === 'completed' || g.status === 'final') || []
})
-// Re-fetch on client if data is stale (handles post-OAuth client-side navigation)
+// Initialize schedule on mount (since it's the default tab)
onMounted(async () => {
- // Only re-fetch if we have no games but are authenticated
+ // Initialize schedule data since Schedule is the default tab
+ if (activeTab.value === 'schedule') {
+ schedule.initialize()
+ }
+
+ // Only re-fetch games if we have no games but are authenticated
// This handles the case where client-side navigation doesn't trigger SSR
if ((!games.value || games.value.length === 0) && authStore.isAuthenticated) {
console.log('[Games Page] No games on mount, re-fetching...')
diff --git a/frontend-sba/types/index.ts b/frontend-sba/types/index.ts
index 9454523..3ecb6ec 100644
--- a/frontend-sba/types/index.ts
+++ b/frontend-sba/types/index.ts
@@ -110,3 +110,11 @@ export type {
RefreshTokenRequest,
RefreshTokenResponse,
} from './api'
+
+// Schedule types
+export type {
+ SbaCurrent,
+ SbaScheduledGame,
+ GetCurrentResponse,
+ GetScheduleGamesResponse,
+} from './schedule'
diff --git a/frontend-sba/types/schedule.ts b/frontend-sba/types/schedule.ts
new file mode 100644
index 0000000..2149a19
--- /dev/null
+++ b/frontend-sba/types/schedule.ts
@@ -0,0 +1,74 @@
+/**
+ * SBA Schedule Types
+ *
+ * Types for schedule data from SBA production API.
+ */
+
+/**
+ * Current season and week from /current endpoint
+ */
+export interface SbaCurrent {
+ id: number
+ season: number
+ week: number
+ freeze: boolean
+ transcount: number
+ bstatcount: number
+ pstatcount: number
+ bet_week: number
+ trade_deadline: number
+ pick_trade_start: number
+ pick_trade_end: number
+ playoffs_begin: number
+ injury_count: number
+}
+
+/**
+ * Team info nested in scheduled game
+ */
+export interface SbaScheduledTeam {
+ id: number
+ abbrev: string
+ sname: string // Short name e.g., "Cardinals"
+ lname: string // Long name e.g., "St. Louis Cardinals"
+ thumbnail?: string
+ color?: string
+}
+
+/**
+ * Scheduled game from /games endpoint
+ *
+ * Games are returned with nested away_team and home_team objects.
+ */
+export interface SbaScheduledGame {
+ // Game identity
+ id: number
+ season: number
+ week: number
+ game_num: number | null // Game number within the week
+ season_type: string // 'regular', 'playoff', etc.
+
+ // Nested team objects
+ away_team: SbaScheduledTeam
+ home_team: SbaScheduledTeam
+
+ // Scores (null if not played)
+ away_score: number | null
+ home_score: number | null
+
+ // Other fields
+ scorecard_url: string | null
+
+ // Computed helper properties (for backward compatibility with simpler interface)
+ // These are accessed via getter functions in the component
+}
+
+/**
+ * Schedule API response types
+ */
+export interface GetCurrentResponse extends SbaCurrent {}
+
+export interface GetScheduleGamesResponse {
+ games: SbaScheduledGame[]
+ count?: number
+}