Refactor travel segments and simplify trip options
Travel segment architecture: - Remove departureTime/arrivalTime from TravelSegment (location-based, not date-based) - Fix travel sections appearing after destination instead of between cities - Fix missing travel segments when revisiting same city (consecutive grouping) - Remove unwanted rest day at end of trip Planning engine fixes: - All three planners now group only consecutive games at same stadium - Visiting A → B → A creates 3 stops with proper travel between UI simplification: - Remove redundant sort options (mostDriving/leastDriving, mostCities/leastCities) - Remove unused "Find Other Sports Along Route" toggle (was dead code) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -226,63 +226,84 @@ final class ScenarioAPlanner: ScenarioPlanner {
|
||||
/// Stop 1: Los Angeles (contains game 1 and 2)
|
||||
/// Stop 2: San Francisco (contains game 3)
|
||||
///
|
||||
/// Note: If you visit the same city, leave, and come back, that creates
|
||||
/// separate stops (one for each visit).
|
||||
///
|
||||
private func buildStops(
|
||||
from games: [Game],
|
||||
stadiums: [UUID: Stadium]
|
||||
) -> [ItineraryStop] {
|
||||
guard !games.isEmpty else { return [] }
|
||||
|
||||
// Step 1: Group all games by their stadium
|
||||
// This lets us find ALL games at a stadium when we create that stop
|
||||
// Result: { stadiumId: [game1, game2, ...], ... }
|
||||
var stadiumGames: [UUID: [Game]] = [:]
|
||||
for game in games {
|
||||
stadiumGames[game.stadiumId, default: []].append(game)
|
||||
// Sort games chronologically
|
||||
let sortedGames = games.sorted { $0.startTime < $1.startTime }
|
||||
|
||||
// Group consecutive games at the same stadium into stops
|
||||
// If you visit A, then B, then A again, that's 3 stops (A, B, A)
|
||||
var stops: [ItineraryStop] = []
|
||||
var currentStadiumId: UUID? = nil
|
||||
var currentGames: [Game] = []
|
||||
|
||||
for game in sortedGames {
|
||||
if game.stadiumId == currentStadiumId {
|
||||
// Same stadium as previous game - add to current group
|
||||
currentGames.append(game)
|
||||
} else {
|
||||
// Different stadium - finalize previous stop (if any) and start new one
|
||||
if let stadiumId = currentStadiumId, !currentGames.isEmpty {
|
||||
if let stop = createStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums) {
|
||||
stops.append(stop)
|
||||
}
|
||||
}
|
||||
currentStadiumId = game.stadiumId
|
||||
currentGames = [game]
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Walk through games in chronological order
|
||||
// When we hit a stadium for the first time, create a stop with ALL games at that stadium
|
||||
var stops: [ItineraryStop] = []
|
||||
var processedStadiums: Set<UUID> = [] // Track which stadiums we've already made stops for
|
||||
|
||||
for game in games {
|
||||
// Skip if we already created a stop for this stadium
|
||||
guard !processedStadiums.contains(game.stadiumId) else { continue }
|
||||
processedStadiums.insert(game.stadiumId)
|
||||
|
||||
// Get ALL games at this stadium (not just this one)
|
||||
let gamesAtStadium = stadiumGames[game.stadiumId] ?? [game]
|
||||
let sortedGames = gamesAtStadium.sorted { $0.startTime < $1.startTime }
|
||||
|
||||
// Look up stadium info for location data
|
||||
let stadium = stadiums[game.stadiumId]
|
||||
let city = stadium?.city ?? "Unknown"
|
||||
let state = stadium?.state ?? ""
|
||||
let coordinate = stadium?.coordinate
|
||||
|
||||
let location = LocationInput(
|
||||
name: city,
|
||||
coordinate: coordinate,
|
||||
address: stadium?.fullAddress
|
||||
)
|
||||
|
||||
// Create the stop
|
||||
// - arrivalDate: when we need to arrive (first game at this stop)
|
||||
// - departureDate: when we can leave (after last game at this stop)
|
||||
// - games: IDs of all games we'll attend at this stop
|
||||
let stop = ItineraryStop(
|
||||
city: city,
|
||||
state: state,
|
||||
coordinate: coordinate,
|
||||
games: sortedGames.map { $0.id },
|
||||
arrivalDate: sortedGames.first?.gameDate ?? Date(),
|
||||
departureDate: sortedGames.last?.gameDate ?? Date(),
|
||||
location: location,
|
||||
firstGameStart: sortedGames.first?.startTime
|
||||
)
|
||||
stops.append(stop)
|
||||
// Don't forget the last group
|
||||
if let stadiumId = currentStadiumId, !currentGames.isEmpty {
|
||||
if let stop = createStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums) {
|
||||
stops.append(stop)
|
||||
}
|
||||
}
|
||||
|
||||
return stops
|
||||
}
|
||||
|
||||
/// Creates an ItineraryStop from a group of games at the same stadium.
|
||||
private func createStop(
|
||||
from games: [Game],
|
||||
stadiumId: UUID,
|
||||
stadiums: [UUID: Stadium]
|
||||
) -> ItineraryStop? {
|
||||
guard !games.isEmpty else { return nil }
|
||||
|
||||
let sortedGames = games.sorted { $0.startTime < $1.startTime }
|
||||
let stadium = stadiums[stadiumId]
|
||||
let city = stadium?.city ?? "Unknown"
|
||||
let state = stadium?.state ?? ""
|
||||
let coordinate = stadium?.coordinate
|
||||
|
||||
let location = LocationInput(
|
||||
name: city,
|
||||
coordinate: coordinate,
|
||||
address: stadium?.fullAddress
|
||||
)
|
||||
|
||||
// departureDate is day AFTER last game (we leave the next morning)
|
||||
let lastGameDate = sortedGames.last?.gameDate ?? Date()
|
||||
let departureDateValue = Calendar.current.date(byAdding: .day, value: 1, to: lastGameDate) ?? lastGameDate
|
||||
|
||||
return ItineraryStop(
|
||||
city: city,
|
||||
state: state,
|
||||
coordinate: coordinate,
|
||||
games: sortedGames.map { $0.id },
|
||||
arrivalDate: sortedGames.first?.gameDate ?? Date(),
|
||||
departureDate: departureDateValue,
|
||||
location: location,
|
||||
firstGameStart: sortedGames.first?.startTime
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user