From 74fd21590b27bc66ee2a1045cb888ca50c5b3a18 Mon Sep 17 00:00:00 2001 From: Trey t Date: Tue, 20 Jan 2026 22:26:48 -0600 Subject: [PATCH] wip --- .../Schedule/Views/ScheduleListView.swift | 95 ++++++++++++++++ .../Views/Wizard/Steps/GamePickerStep.swift | 104 +++++++++++++++++- .../Trip/Views/Wizard/TripWizardView.swift | 4 +- 3 files changed, 201 insertions(+), 2 deletions(-) diff --git a/SportsTime/Features/Schedule/Views/ScheduleListView.swift b/SportsTime/Features/Schedule/Views/ScheduleListView.swift index 2482d9b..f1ecd8d 100644 --- a/SportsTime/Features/Schedule/Views/ScheduleListView.swift +++ b/SportsTime/Features/Schedule/Views/ScheduleListView.swift @@ -9,6 +9,7 @@ struct ScheduleListView: View { @Environment(\.colorScheme) private var colorScheme @State private var viewModel = ScheduleViewModel() @State private var showDatePicker = false + @State private var showDiagnostics = false private var allGames: [RichGame] { viewModel.gamesBySport.flatMap(\.games) @@ -45,11 +46,22 @@ struct ScheduleListView: View { Label("Clear Filters", systemImage: "xmark.circle") } } + + Divider() + + Button { + showDiagnostics = true + } label: { + Label("Diagnostics", systemImage: "info.circle") + } } label: { Image(systemName: viewModel.hasFilters ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle") } } } + .sheet(isPresented: $showDiagnostics) { + ScheduleDiagnosticsSheet(diagnostics: viewModel.diagnostics) + } .sheet(isPresented: $showDatePicker) { DateRangePickerSheet( startDate: viewModel.startDate, @@ -374,6 +386,89 @@ struct DateRangePickerSheet: View { } } +// MARK: - Schedule Diagnostics Sheet + +struct ScheduleDiagnosticsSheet: View { + @Environment(\.dismiss) private var dismiss + let diagnostics: ScheduleDiagnostics + + var body: some View { + NavigationStack { + List { + Section("Query Details") { + if let start = diagnostics.lastQueryStartDate, + let end = diagnostics.lastQueryEndDate { + LabeledContent("Start Date") { + Text(start.formatted(date: .abbreviated, time: .shortened)) + } + LabeledContent("End Date") { + Text(end.formatted(date: .abbreviated, time: .shortened)) + } + } else { + Text("No query executed") + .foregroundStyle(.secondary) + } + + if !diagnostics.lastQuerySports.isEmpty { + LabeledContent("Sports Filter") { + Text(diagnostics.lastQuerySports.map(\.rawValue).joined(separator: ", ")) + .font(.caption) + } + } + } + + Section("Data Loaded") { + LabeledContent("Teams", value: "\(diagnostics.teamsLoaded)") + LabeledContent("Stadiums", value: "\(diagnostics.stadiumsLoaded)") + LabeledContent("Total Games", value: "\(diagnostics.totalGamesReturned)") + } + + if !diagnostics.gamesBySport.isEmpty { + Section("Games by Sport") { + ForEach(diagnostics.gamesBySport.sorted(by: { $0.key.rawValue < $1.key.rawValue }), id: \.key) { sport, count in + LabeledContent { + Text("\(count)") + .fontWeight(.semibold) + } label: { + HStack { + Image(systemName: sport.iconName) + .foregroundStyle(sport.themeColor) + Text(sport.rawValue) + } + } + } + } + } + + Section("Troubleshooting") { + Text("If games are missing:") + .font(.subheadline) + .fontWeight(.medium) + + VStack(alignment: .leading, spacing: 8) { + Label("Check the date range above matches your expectations", systemImage: "1.circle") + Label("Verify the sport is enabled in the filter", systemImage: "2.circle") + Label("Pull down to refresh the schedule", systemImage: "3.circle") + Label("Check Settings > Debug > CloudKit Sync for sync status", systemImage: "4.circle") + } + .font(.caption) + .foregroundStyle(.secondary) + } + } + .navigationTitle("Schedule Diagnostics") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + dismiss() + } + } + } + } + .presentationDetents([.medium, .large]) + } +} + #Preview { NavigationStack { ScheduleListView() diff --git a/SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swift b/SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swift index 4366137..1fbb8bc 100644 --- a/SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swift +++ b/SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swift @@ -14,6 +14,8 @@ struct GamePickerStep: View { @Binding var selectedSports: Set @Binding var selectedTeamIds: Set @Binding var selectedGameIds: Set + @Binding var startDate: Date + @Binding var endDate: Date @State private var showSportsPicker = false @State private var showTeamsPicker = false @@ -68,10 +70,18 @@ struct GamePickerStep: View { // Selected Games Summary if !selectedGameIds.isEmpty { selectedGamesSummary + + // Date Range Section - shown when games are selected + dateRangeSection } } .padding(Theme.Spacing.lg) .background(Theme.cardBackground(colorScheme)) + .onChange(of: selectedGameIds) { _, newValue in + Task { + await updateDateRangeForSelectedGames() + } + } .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.large) @@ -221,6 +231,93 @@ struct GamePickerStep: View { summaryGames = Array(Set(games)) } } + + // MARK: - Date Range Section + + private var dateRangeSection: some View { + VStack(alignment: .leading, spacing: Theme.Spacing.sm) { + HStack { + Image(systemName: "calendar") + .foregroundStyle(Theme.warmOrange) + Text("Trip Date Range") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(Theme.textPrimary(colorScheme)) + + Spacer() + + // Show auto-calculated indicator + Text("Auto-adjusted") + .font(.caption2) + .foregroundStyle(Theme.textMuted(colorScheme)) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Theme.cardBackgroundElevated(colorScheme)) + .clipShape(Capsule()) + } + + DateRangePicker(startDate: $startDate, endDate: $endDate) + + // Game date markers legend + if !summaryGames.isEmpty { + let selectedGamesWithDates = summaryGames.filter { selectedGameIds.contains($0.id) } + if !selectedGamesWithDates.isEmpty { + VStack(alignment: .leading, spacing: 4) { + Text("Game dates:") + .font(.caption2) + .foregroundStyle(Theme.textMuted(colorScheme)) + + ForEach(selectedGamesWithDates.sorted { $0.game.dateTime < $1.game.dateTime }) { game in + HStack(spacing: 6) { + Circle() + .fill(game.game.sport.themeColor) + .frame(width: 6, height: 6) + Text("\(game.matchupDescription) - \(game.game.dateTime, style: .date)") + .font(.caption2) + .foregroundStyle(Theme.textSecondary(colorScheme)) + } + } + } + .padding(.top, Theme.Spacing.xs) + } + } + } + .padding(Theme.Spacing.md) + .background(Theme.cardBackgroundElevated(colorScheme)) + .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) + } + + /// Updates the date range based on selected games + /// - Single game: 7-day span centered on game (position 4 of 7, so game is day 4) + /// - Multiple games: range from earliest to latest with 1-day buffer + private func updateDateRangeForSelectedGames() async { + let selectedGames = summaryGames.filter { selectedGameIds.contains($0.id) } + + guard !selectedGames.isEmpty else { return } + + let gameDates = selectedGames.map { $0.game.dateTime }.sorted() + let calendar = Calendar.current + + await MainActor.run { + if gameDates.count == 1 { + // Single game: 7-day span centered on game + // Position 4 of 7 means: 3 days before, game day, 3 days after + let gameDate = gameDates[0] + let newStart = calendar.date(byAdding: .day, value: -3, to: gameDate) ?? gameDate + let newEnd = calendar.date(byAdding: .day, value: 3, to: gameDate) ?? gameDate + startDate = calendar.startOfDay(for: newStart) + endDate = calendar.startOfDay(for: newEnd) + } else { + // Multiple games: span from first to last with 1-day buffer + let firstGameDate = gameDates.first! + let lastGameDate = gameDates.last! + let newStart = calendar.date(byAdding: .day, value: -1, to: firstGameDate) ?? firstGameDate + let newEnd = calendar.date(byAdding: .day, value: 1, to: lastGameDate) ?? lastGameDate + startDate = calendar.startOfDay(for: newStart) + endDate = calendar.startOfDay(for: newEnd) + } + } + } } // MARK: - Sports Picker Sheet @@ -265,6 +362,7 @@ private struct SportsPickerSheet: View { } } .padding(.vertical, Theme.Spacing.xs) + .contentShape(Rectangle()) } .buttonStyle(.plain) } @@ -362,6 +460,7 @@ private struct TeamsPickerSheet: View { } } .padding(.vertical, Theme.Spacing.xs) + .contentShape(Rectangle()) } .buttonStyle(.plain) } @@ -465,6 +564,7 @@ private struct GamesPickerSheet: View { } } .padding(.vertical, Theme.Spacing.xs) + .contentShape(Rectangle()) } .buttonStyle(.plain) } @@ -517,7 +617,9 @@ private struct GamesPickerSheet: View { GamePickerStep( selectedSports: .constant([.mlb]), selectedTeamIds: .constant([]), - selectedGameIds: .constant([]) + selectedGameIds: .constant([]), + startDate: .constant(Date()), + endDate: .constant(Date().addingTimeInterval(86400 * 7)) ) .padding() .themedBackground() diff --git a/SportsTime/Features/Trip/Views/Wizard/TripWizardView.swift b/SportsTime/Features/Trip/Views/Wizard/TripWizardView.swift index 8837b51..38550e0 100644 --- a/SportsTime/Features/Trip/Views/Wizard/TripWizardView.swift +++ b/SportsTime/Features/Trip/Views/Wizard/TripWizardView.swift @@ -40,7 +40,9 @@ struct TripWizardView: View { GamePickerStep( selectedSports: $viewModel.gamePickerSports, selectedTeamIds: $viewModel.gamePickerTeamIds, - selectedGameIds: $viewModel.selectedGameIds + selectedGameIds: $viewModel.selectedGameIds, + startDate: $viewModel.startDate, + endDate: $viewModel.endDate ) }