fix(planning): gameFirst mode now uses full date range and shows correct month
Two bugs fixed in "By Games" trip planning mode: 1. Calendar navigation: DateRangePicker now navigates to the selected game's month when startDate changes externally, instead of staying on the current month. 2. Date range calculation: Fixed race condition where date range was calculated before games were loaded. Now updateDateRangeForSelectedGames() is called after loadSummaryGames() completes. 3. Bonus games: planTrip() now uses the UI-selected 7-day date range instead of overriding it with just the anchor game dates. This allows ScenarioBPlanner to find additional games within the trip window. Added regression tests to verify gameFirst mode includes bonus games. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,25 @@ struct DateRangePicker: View {
|
||||
selectionState = .complete
|
||||
}
|
||||
}
|
||||
.onChange(of: startDate) { oldValue, newValue in
|
||||
// Navigate calendar to show the new month when startDate changes externally
|
||||
// (e.g., when user selects a game in GamePickerStep)
|
||||
let oldMonth = calendar.component(.month, from: oldValue)
|
||||
let newMonth = calendar.component(.month, from: newValue)
|
||||
let oldYear = calendar.component(.year, from: oldValue)
|
||||
let newYear = calendar.component(.year, from: newValue)
|
||||
|
||||
if oldMonth != newMonth || oldYear != newYear {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
displayedMonth = calendar.startOfDay(for: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Also update selection state to complete if we have a valid range
|
||||
if endDate > newValue {
|
||||
selectionState = .complete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedRangeSummary: some View {
|
||||
|
||||
@@ -77,11 +77,6 @@ struct GamePickerStep: View {
|
||||
}
|
||||
.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)
|
||||
@@ -230,6 +225,8 @@ struct GamePickerStep: View {
|
||||
await MainActor.run {
|
||||
summaryGames = Array(Set(games))
|
||||
}
|
||||
// Update date range after games are loaded (not before)
|
||||
await updateDateRangeForSelectedGames()
|
||||
}
|
||||
|
||||
// MARK: - Date Range Section
|
||||
|
||||
@@ -164,13 +164,15 @@ struct TripWizardView: View {
|
||||
let teamsById = Dictionary(uniqueKeysWithValues: AppDataProvider.shared.teams.map { ($0.id, $0) })
|
||||
let stadiumsById = Dictionary(uniqueKeysWithValues: AppDataProvider.shared.stadiums.map { ($0.id, $0) })
|
||||
|
||||
// For gameFirst mode, derive date range from selected games
|
||||
// For gameFirst mode, use the UI-selected date range (set by GamePickerStep)
|
||||
// The date range is a 7-day span centered on the selected game(s)
|
||||
var games: [Game]
|
||||
if viewModel.planningMode == .gameFirst && !viewModel.selectedGameIds.isEmpty {
|
||||
// Fetch all games for the selected sports to find the must-see games
|
||||
// Fetch all games for the selected sports within the UI date range
|
||||
// GamePickerStep already set viewModel.startDate/endDate to a 7-day span
|
||||
let allGames = try await AppDataProvider.shared.allGames(for: preferences.sports)
|
||||
|
||||
// Find the selected must-see games
|
||||
// Validate that selected must-see games exist
|
||||
let mustSeeGames = allGames.filter { viewModel.selectedGameIds.contains($0.id) }
|
||||
|
||||
if mustSeeGames.isEmpty {
|
||||
@@ -179,30 +181,14 @@ struct TripWizardView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// Derive date range from must-see games (with buffer)
|
||||
let gameDates = mustSeeGames.map { $0.dateTime }
|
||||
let minDate = gameDates.min() ?? Date()
|
||||
let maxDate = gameDates.max() ?? Date()
|
||||
// Use the UI-selected date range (already set by GamePickerStep to 7-day span)
|
||||
// Filter all games within this range - ScenarioBPlanner will use anchor games
|
||||
// as required stops and add bonus games that fit geographically
|
||||
let rangeStart = Calendar.current.startOfDay(for: preferences.startDate)
|
||||
let rangeEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: preferences.endDate) ?? preferences.endDate
|
||||
|
||||
// Update preferences with derived date range
|
||||
preferences = TripPreferences(
|
||||
planningMode: preferences.planningMode,
|
||||
startLocation: preferences.startLocation,
|
||||
endLocation: preferences.endLocation,
|
||||
sports: preferences.sports,
|
||||
mustSeeGameIds: preferences.mustSeeGameIds,
|
||||
startDate: Calendar.current.startOfDay(for: minDate),
|
||||
endDate: Calendar.current.date(byAdding: .day, value: 1, to: maxDate) ?? maxDate,
|
||||
mustStopLocations: preferences.mustStopLocations,
|
||||
routePreference: preferences.routePreference,
|
||||
allowRepeatCities: preferences.allowRepeatCities,
|
||||
selectedRegions: preferences.selectedRegions,
|
||||
followTeamId: preferences.followTeamId
|
||||
)
|
||||
|
||||
// Use all games within the derived date range
|
||||
games = allGames.filter {
|
||||
$0.dateTime >= preferences.startDate && $0.dateTime <= preferences.endDate
|
||||
$0.dateTime >= rangeStart && $0.dateTime <= rangeEnd
|
||||
}
|
||||
} else {
|
||||
// Standard mode: fetch games for date range
|
||||
|
||||
Reference in New Issue
Block a user