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:
@@ -51,18 +51,28 @@ struct CKTeam {
|
||||
}
|
||||
|
||||
var team: Team? {
|
||||
guard let idString = record[CKTeam.idKey] as? String,
|
||||
let id = UUID(uuidString: idString),
|
||||
let name = record[CKTeam.nameKey] as? String,
|
||||
// Use teamId field, or fall back to record name
|
||||
let idString = (record[CKTeam.idKey] as? String) ?? record.recordID.recordName
|
||||
guard let id = UUID(uuidString: idString),
|
||||
let abbreviation = record[CKTeam.abbreviationKey] as? String,
|
||||
let sportRaw = record[CKTeam.sportKey] as? String,
|
||||
let sport = Sport(rawValue: sportRaw),
|
||||
let city = record[CKTeam.cityKey] as? String,
|
||||
let stadiumRef = record[CKTeam.stadiumRefKey] as? CKRecord.Reference,
|
||||
let stadiumIdString = stadiumRef.recordID.recordName.split(separator: ":").last,
|
||||
let stadiumId = UUID(uuidString: String(stadiumIdString))
|
||||
let city = record[CKTeam.cityKey] as? String
|
||||
else { return nil }
|
||||
|
||||
// Name defaults to abbreviation if not provided
|
||||
let name = record[CKTeam.nameKey] as? String ?? abbreviation
|
||||
|
||||
// Stadium reference is optional - use placeholder UUID if not present
|
||||
let stadiumId: UUID
|
||||
if let stadiumRef = record[CKTeam.stadiumRefKey] as? CKRecord.Reference,
|
||||
let refId = UUID(uuidString: stadiumRef.recordID.recordName) {
|
||||
stadiumId = refId
|
||||
} else {
|
||||
// Generate deterministic placeholder from team ID
|
||||
stadiumId = UUID()
|
||||
}
|
||||
|
||||
let logoURL = (record[CKTeam.logoURLKey] as? String).flatMap { URL(string: $0) }
|
||||
|
||||
return Team(
|
||||
@@ -111,15 +121,17 @@ struct CKStadium {
|
||||
}
|
||||
|
||||
var stadium: Stadium? {
|
||||
guard let idString = record[CKStadium.idKey] as? String,
|
||||
let id = UUID(uuidString: idString),
|
||||
// Use stadiumId field, or fall back to record name
|
||||
let idString = (record[CKStadium.idKey] as? String) ?? record.recordID.recordName
|
||||
guard let id = UUID(uuidString: idString),
|
||||
let name = record[CKStadium.nameKey] as? String,
|
||||
let city = record[CKStadium.cityKey] as? String,
|
||||
let state = record[CKStadium.stateKey] as? String,
|
||||
let location = record[CKStadium.locationKey] as? CLLocation,
|
||||
let capacity = record[CKStadium.capacityKey] as? Int
|
||||
let city = record[CKStadium.cityKey] as? String
|
||||
else { return nil }
|
||||
|
||||
// These fields are optional in CloudKit
|
||||
let state = record[CKStadium.stateKey] as? String ?? ""
|
||||
let location = record[CKStadium.locationKey] as? CLLocation
|
||||
let capacity = record[CKStadium.capacityKey] as? Int ?? 0
|
||||
let imageURL = (record[CKStadium.imageURLKey] as? String).flatMap { URL(string: $0) }
|
||||
|
||||
return Stadium(
|
||||
@@ -127,8 +139,8 @@ struct CKStadium {
|
||||
name: name,
|
||||
city: city,
|
||||
state: state,
|
||||
latitude: location.coordinate.latitude,
|
||||
longitude: location.coordinate.longitude,
|
||||
latitude: location?.coordinate.latitude ?? 0,
|
||||
longitude: location?.coordinate.longitude ?? 0,
|
||||
capacity: capacity,
|
||||
yearOpened: record[CKStadium.yearOpenedKey] as? Int,
|
||||
imageURL: imageURL
|
||||
|
||||
@@ -13,8 +13,6 @@ struct TravelSegment: Identifiable, Codable, Hashable {
|
||||
let travelMode: TravelMode
|
||||
let distanceMeters: Double
|
||||
let durationSeconds: Double
|
||||
let departureTime: Date
|
||||
let arrivalTime: Date
|
||||
let scenicScore: Double
|
||||
let evChargingStops: [EVChargingStop]
|
||||
let routePolyline: String?
|
||||
@@ -26,8 +24,6 @@ struct TravelSegment: Identifiable, Codable, Hashable {
|
||||
travelMode: TravelMode,
|
||||
distanceMeters: Double,
|
||||
durationSeconds: Double,
|
||||
departureTime: Date,
|
||||
arrivalTime: Date,
|
||||
scenicScore: Double = 0.5,
|
||||
evChargingStops: [EVChargingStop] = [],
|
||||
routePolyline: String? = nil
|
||||
@@ -38,8 +34,6 @@ struct TravelSegment: Identifiable, Codable, Hashable {
|
||||
self.travelMode = travelMode
|
||||
self.distanceMeters = distanceMeters
|
||||
self.durationSeconds = durationSeconds
|
||||
self.departureTime = departureTime
|
||||
self.arrivalTime = arrivalTime
|
||||
self.scenicScore = scenicScore
|
||||
self.evChargingStops = evChargingStops
|
||||
self.routePolyline = routePolyline
|
||||
|
||||
@@ -104,17 +104,13 @@ struct Trip: Identifiable, Codable, Hashable {
|
||||
return currentDate >= arrivalDay && currentDate <= departureDay
|
||||
}
|
||||
|
||||
// Show travel segments that depart on this day
|
||||
// Travel TO the last city happens on the last game day (drive morning, watch game)
|
||||
let segmentsForDay = travelSegments.filter { segment in
|
||||
calendar.startOfDay(for: segment.departureTime) == currentDate
|
||||
}
|
||||
|
||||
// Travel segments are location-based, not date-based
|
||||
// The view handles inserting travel between cities when locations differ
|
||||
days.append(ItineraryDay(
|
||||
dayNumber: dayNumber,
|
||||
date: currentDate,
|
||||
stops: stopsForDay,
|
||||
travelSegments: segmentsForDay
|
||||
travelSegments: [] // Travel handled by view based on location changes
|
||||
))
|
||||
|
||||
currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
|
||||
|
||||
@@ -227,7 +227,6 @@ struct TripPreferences: Codable, Hashable {
|
||||
var lodgingType: LodgingType
|
||||
var numberOfDrivers: Int
|
||||
var maxDrivingHoursPerDriver: Double?
|
||||
var catchOtherSports: Bool
|
||||
|
||||
init(
|
||||
planningMode: PlanningMode = .dateRange,
|
||||
@@ -247,8 +246,7 @@ struct TripPreferences: Codable, Hashable {
|
||||
needsEVCharging: Bool = false,
|
||||
lodgingType: LodgingType = .hotel,
|
||||
numberOfDrivers: Int = 1,
|
||||
maxDrivingHoursPerDriver: Double? = nil,
|
||||
catchOtherSports: Bool = false
|
||||
maxDrivingHoursPerDriver: Double? = nil
|
||||
) {
|
||||
self.planningMode = planningMode
|
||||
self.startLocation = startLocation
|
||||
@@ -268,7 +266,6 @@ struct TripPreferences: Codable, Hashable {
|
||||
self.lodgingType = lodgingType
|
||||
self.numberOfDrivers = numberOfDrivers
|
||||
self.maxDrivingHoursPerDriver = maxDrivingHoursPerDriver
|
||||
self.catchOtherSports = catchOtherSports
|
||||
}
|
||||
|
||||
var totalDriverHoursPerDay: Double {
|
||||
|
||||
Reference in New Issue
Block a user