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:
Trey t
2026-01-07 19:39:53 -06:00
parent 40a6f879e3
commit 4184af60b5
29 changed files with 140675 additions and 144310 deletions

View File

@@ -262,24 +262,47 @@ struct TripDetailView: View {
}
}
/// Build itinerary sections: days and travel between days
/// Build itinerary sections: days with travel between different cities
private var itinerarySections: [ItinerarySection] {
var sections: [ItinerarySection] = []
let calendar = Calendar.current
// Build day sections for days with games
var daySections: [(dayNumber: Int, date: Date, city: String, games: [RichGame])] = []
let days = tripDays
for (index, dayDate) in days.enumerated() {
let dayNum = index + 1
let gamesOnDay = gamesOn(date: dayDate)
if !gamesOnDay.isEmpty || index == 0 || index == days.count - 1 {
sections.append(.day(dayNumber: dayNum, date: dayDate, games: gamesOnDay))
// Get city from games (preferred) or from stops as fallback
let cityForDay = gamesOnDay.first?.stadium.city ?? cityOn(date: dayDate) ?? ""
// Include days with games
// Skip empty days at the end (departure day after last game)
if !gamesOnDay.isEmpty {
daySections.append((dayNum, dayDate, cityForDay, gamesOnDay))
}
}
// Build sections: insert travel BEFORE each day when coming from different city
for (index, daySection) in daySections.enumerated() {
// Check if we need travel BEFORE this day (coming from different city)
if index > 0 {
let prevSection = daySections[index - 1]
let prevCity = prevSection.city
let currentCity = daySection.city
// If cities differ, find travel segment from prev -> current
if !prevCity.isEmpty && !currentCity.isEmpty && prevCity != currentCity {
if let travelSegment = findTravelSegment(from: prevCity, to: currentCity) {
sections.append(.travel(travelSegment))
}
}
}
let travelAfterDay = travelDepartingAfter(date: dayDate, beforeNextGameDay: days.indices.contains(index + 1) ? days[index + 1] : nil)
for segment in travelAfterDay {
sections.append(.travel(segment))
}
// Add the day section
sections.append(.day(dayNumber: daySection.dayNumber, date: daySection.date, games: daySection.games))
}
return sections
@@ -311,13 +334,27 @@ struct TripDetailView: View {
}.sorted { $0.game.dateTime < $1.game.dateTime }
}
private func travelDepartingAfter(date: Date, beforeNextGameDay: Date?) -> [TravelSegment] {
/// Get the city for a given date (from the stop that covers that date)
private func cityOn(date: Date) -> String? {
let calendar = Calendar.current
let dayEnd = calendar.startOfDay(for: date)
let dayStart = calendar.startOfDay(for: date)
return trip.travelSegments.filter { segment in
let segmentDay = calendar.startOfDay(for: segment.departureTime)
return segmentDay == dayEnd
return trip.stops.first { stop in
let arrivalDay = calendar.startOfDay(for: stop.arrivalDate)
let departureDay = calendar.startOfDay(for: stop.departureDate)
return dayStart >= arrivalDay && dayStart <= departureDay
}?.city
}
/// Find travel segment that goes from one city to another
private func findTravelSegment(from fromCity: String, to toCity: String) -> TravelSegment? {
let fromLower = fromCity.lowercased().trimmingCharacters(in: .whitespaces)
let toLower = toCity.lowercased().trimmingCharacters(in: .whitespaces)
return trip.travelSegments.first { segment in
let segmentFrom = segment.fromLocation.name.lowercased().trimmingCharacters(in: .whitespaces)
let segmentTo = segment.toLocation.name.lowercased().trimmingCharacters(in: .whitespaces)
return segmentFrom == fromLower && segmentTo == toLower
}
}