wip
This commit is contained in:
@@ -9,6 +9,7 @@ struct ScheduleListView: View {
|
|||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
@State private var viewModel = ScheduleViewModel()
|
@State private var viewModel = ScheduleViewModel()
|
||||||
@State private var showDatePicker = false
|
@State private var showDatePicker = false
|
||||||
|
@State private var showDiagnostics = false
|
||||||
|
|
||||||
private var allGames: [RichGame] {
|
private var allGames: [RichGame] {
|
||||||
viewModel.gamesBySport.flatMap(\.games)
|
viewModel.gamesBySport.flatMap(\.games)
|
||||||
@@ -45,11 +46,22 @@ struct ScheduleListView: View {
|
|||||||
Label("Clear Filters", systemImage: "xmark.circle")
|
Label("Clear Filters", systemImage: "xmark.circle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
showDiagnostics = true
|
||||||
|
} label: {
|
||||||
|
Label("Diagnostics", systemImage: "info.circle")
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: viewModel.hasFilters ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle")
|
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) {
|
.sheet(isPresented: $showDatePicker) {
|
||||||
DateRangePickerSheet(
|
DateRangePickerSheet(
|
||||||
startDate: viewModel.startDate,
|
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 {
|
#Preview {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScheduleListView()
|
ScheduleListView()
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ struct GamePickerStep: View {
|
|||||||
@Binding var selectedSports: Set<Sport>
|
@Binding var selectedSports: Set<Sport>
|
||||||
@Binding var selectedTeamIds: Set<String>
|
@Binding var selectedTeamIds: Set<String>
|
||||||
@Binding var selectedGameIds: Set<String>
|
@Binding var selectedGameIds: Set<String>
|
||||||
|
@Binding var startDate: Date
|
||||||
|
@Binding var endDate: Date
|
||||||
|
|
||||||
@State private var showSportsPicker = false
|
@State private var showSportsPicker = false
|
||||||
@State private var showTeamsPicker = false
|
@State private var showTeamsPicker = false
|
||||||
@@ -68,10 +70,18 @@ struct GamePickerStep: View {
|
|||||||
// Selected Games Summary
|
// Selected Games Summary
|
||||||
if !selectedGameIds.isEmpty {
|
if !selectedGameIds.isEmpty {
|
||||||
selectedGamesSummary
|
selectedGamesSummary
|
||||||
|
|
||||||
|
// Date Range Section - shown when games are selected
|
||||||
|
dateRangeSection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Theme.Spacing.lg)
|
.padding(Theme.Spacing.lg)
|
||||||
.background(Theme.cardBackground(colorScheme))
|
.background(Theme.cardBackground(colorScheme))
|
||||||
|
.onChange(of: selectedGameIds) { _, newValue in
|
||||||
|
Task {
|
||||||
|
await updateDateRangeForSelectedGames()
|
||||||
|
}
|
||||||
|
}
|
||||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
|
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
|
RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
|
||||||
@@ -221,6 +231,93 @@ struct GamePickerStep: View {
|
|||||||
summaryGames = Array(Set(games))
|
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
|
// MARK: - Sports Picker Sheet
|
||||||
@@ -265,6 +362,7 @@ private struct SportsPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, Theme.Spacing.xs)
|
.padding(.vertical, Theme.Spacing.xs)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
@@ -362,6 +460,7 @@ private struct TeamsPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, Theme.Spacing.xs)
|
.padding(.vertical, Theme.Spacing.xs)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
@@ -465,6 +564,7 @@ private struct GamesPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, Theme.Spacing.xs)
|
.padding(.vertical, Theme.Spacing.xs)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
@@ -517,7 +617,9 @@ private struct GamesPickerSheet: View {
|
|||||||
GamePickerStep(
|
GamePickerStep(
|
||||||
selectedSports: .constant([.mlb]),
|
selectedSports: .constant([.mlb]),
|
||||||
selectedTeamIds: .constant([]),
|
selectedTeamIds: .constant([]),
|
||||||
selectedGameIds: .constant([])
|
selectedGameIds: .constant([]),
|
||||||
|
startDate: .constant(Date()),
|
||||||
|
endDate: .constant(Date().addingTimeInterval(86400 * 7))
|
||||||
)
|
)
|
||||||
.padding()
|
.padding()
|
||||||
.themedBackground()
|
.themedBackground()
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ struct TripWizardView: View {
|
|||||||
GamePickerStep(
|
GamePickerStep(
|
||||||
selectedSports: $viewModel.gamePickerSports,
|
selectedSports: $viewModel.gamePickerSports,
|
||||||
selectedTeamIds: $viewModel.gamePickerTeamIds,
|
selectedTeamIds: $viewModel.gamePickerTeamIds,
|
||||||
selectedGameIds: $viewModel.selectedGameIds
|
selectedGameIds: $viewModel.selectedGameIds,
|
||||||
|
startDate: $viewModel.startDate,
|
||||||
|
endDate: $viewModel.endDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user