From 5ed4e309bd62872f419c0a837de511a809ff7a06 Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 10 Jan 2026 17:36:22 -0600 Subject: [PATCH] feat(schedule): group games by sport instead of date Games in schedule view now display in sport sections (MLB, NBA, etc.) with games sorted by date within each section. Each game row shows its date since the section header now shows sport instead. Co-Authored-By: Claude Opus 4.5 --- PROJECT_STATE.md | 2 +- .../ViewModels/ScheduleViewModel.swift | 15 +++++++++ .../Schedule/Views/ScheduleListView.swift | 33 +++++++++++++------ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 74842ad..1c624eb 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -37,7 +37,7 @@ Build a functional iOS sports trip planning app that helps users plan multi-stop ## Active Tasks - [x] Fix "By Games" Mode Game Selection -- [ ] Group Schedule View Games by Sport +- [x] Group Schedule View Games by Sport - [ ] Remove Buffer Days from Trip Planner ## Completed Tasks diff --git a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift index 60b6fbd..02270fe 100644 --- a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift +++ b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift @@ -50,6 +50,21 @@ final class ScheduleViewModel { return grouped.sorted { $0.key < $1.key }.map { ($0.key, $0.value) } } + var gamesBySport: [(sport: Sport, games: [RichGame])] { + let grouped = Dictionary(grouping: filteredGames) { game in + game.game.sport + } + // Sort by sport order (use allCases index for consistent ordering) + // Within each sport, games are sorted by date + return grouped + .sorted { lhs, rhs in + let lhsIndex = Sport.allCases.firstIndex(of: lhs.key) ?? 0 + let rhsIndex = Sport.allCases.firstIndex(of: rhs.key) ?? 0 + return lhsIndex < rhsIndex + } + .map { (sport: $0.key, games: $0.value.sorted { $0.game.dateTime < $1.game.dateTime }) } + } + var hasFilters: Bool { selectedSports.count < Sport.supported.count || !searchText.isEmpty } diff --git a/SportsTime/Features/Schedule/Views/ScheduleListView.swift b/SportsTime/Features/Schedule/Views/ScheduleListView.swift index d03e592..2d76cac 100644 --- a/SportsTime/Features/Schedule/Views/ScheduleListView.swift +++ b/SportsTime/Features/Schedule/Views/ScheduleListView.swift @@ -89,14 +89,17 @@ struct ScheduleListView: View { .listRowBackground(Color.clear) } - ForEach(viewModel.gamesByDate, id: \.date) { dateGroup in + ForEach(viewModel.gamesBySport, id: \.sport) { sportGroup in Section { - ForEach(dateGroup.games) { richGame in - GameRowView(game: richGame) + ForEach(sportGroup.games) { richGame in + GameRowView(game: richGame, showDate: true) } } header: { - Text(formatSectionDate(dateGroup.date)) - .font(.headline) + HStack(spacing: 8) { + Image(systemName: sportGroup.sport.iconName) + Text(sportGroup.sport.rawValue) + } + .font(.headline) } .listRowBackground(Theme.cardBackground(colorScheme)) } @@ -204,9 +207,18 @@ struct SportFilterChip: View { struct GameRowView: View { let game: RichGame + var showDate: Bool = false var body: some View { VStack(alignment: .leading, spacing: 8) { + // Date (when grouped by sport) + if showDate { + Text(formattedDate) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + } + // Teams HStack { VStack(alignment: .leading, spacing: 4) { @@ -220,11 +232,6 @@ struct GameRowView: View { } Spacer() - - // Sport badge - Image(systemName: game.game.sport.iconName) - .font(.caption) - .foregroundStyle(.secondary) } // Game info @@ -241,6 +248,12 @@ struct GameRowView: View { } .padding(.vertical, 4) } + + private var formattedDate: String { + let formatter = DateFormatter() + formatter.dateFormat = "EEE, MMM d" + return formatter.string(from: game.game.dateTime) + } } // MARK: - Team Badge