fix(itinerary): add city to game items for proper constraint validation
Travel constraint validation was not working because ItineraryConstraints had no game items to validate against - games came from RichGame objects but were never converted to ItineraryItem for constraint checking. Changes: - Add city parameter to ItemKind.game enum case - Create game ItineraryItems from RichGame data in buildItineraryData() - Update isValidTravelPosition to compare against actual game sortOrders - Fix tests to use appropriate game sortOrder conventions Now travel is properly constrained to appear before arrival city games and after departure city games. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,14 +80,14 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
|
||||
hostingController.view.translatesAutoresizingMaskIntoConstraints = true
|
||||
|
||||
controller.setTableHeader(hostingController.view)
|
||||
|
||||
|
||||
// Load initial data
|
||||
let (days, validRanges, allItemsForConstraints) = buildItineraryData()
|
||||
controller.reloadData(days: days, travelValidRanges: validRanges, itineraryItems: allItemsForConstraints)
|
||||
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
||||
func updateUIViewController(_ controller: ItineraryTableViewController, context: Context) {
|
||||
controller.colorScheme = colorScheme
|
||||
controller.onTravelMoved = onTravelMoved
|
||||
@@ -95,37 +95,46 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
|
||||
controller.onCustomItemTapped = onCustomItemTapped
|
||||
controller.onCustomItemDeleted = onCustomItemDeleted
|
||||
controller.onAddButtonTapped = onAddButtonTapped
|
||||
|
||||
|
||||
// Update header content by updating the hosting controller's rootView
|
||||
// This avoids recreating the view hierarchy and prevents infinite loops
|
||||
context.coordinator.headerHostingController?.rootView = headerContent
|
||||
|
||||
|
||||
let (days, validRanges, allItemsForConstraints) = buildItineraryData()
|
||||
controller.reloadData(days: days, travelValidRanges: validRanges, itineraryItems: allItemsForConstraints)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Build Itinerary Data
|
||||
|
||||
|
||||
private func buildItineraryData() -> ([ItineraryDayData], [String: ClosedRange<Int>], [ItineraryItem]) {
|
||||
let tripDays = calculateTripDays()
|
||||
var travelValidRanges: [String: ClosedRange<Int>] = [:]
|
||||
|
||||
|
||||
// Build game items from RichGame data for constraint validation
|
||||
var gameItems: [ItineraryItem] = []
|
||||
for (index, dayDate) in tripDays.enumerated() {
|
||||
let dayNum = index + 1
|
||||
let gamesOnDay = gamesOn(date: dayDate)
|
||||
for (gameIndex, richGame) in gamesOnDay.enumerated() {
|
||||
let gameItem = ItineraryItem(
|
||||
tripId: trip.id,
|
||||
day: dayNum,
|
||||
sortOrder: Double(gameIndex) * 0.01, // Games have sortOrder ~0 (at the visual boundary)
|
||||
kind: .game(gameId: richGame.game.id, city: richGame.stadium.city)
|
||||
)
|
||||
gameItems.append(gameItem)
|
||||
}
|
||||
}
|
||||
|
||||
// Build travel as semantic items with (day, sortOrder)
|
||||
var travelItems: [ItineraryItem] = []
|
||||
travelItems.reserveCapacity(trip.travelSegments.count)
|
||||
|
||||
func cityFromGameId(_ gameId: String) -> String? {
|
||||
let comps = gameId.components(separatedBy: "-")
|
||||
guard comps.count >= 2 else { return nil }
|
||||
return comps[1]
|
||||
}
|
||||
|
||||
|
||||
func gamesIn(city: String, day: Int) -> [ItineraryItem] {
|
||||
itineraryItems.filter { item in
|
||||
gameItems.filter { item in
|
||||
guard item.day == day else { return false }
|
||||
guard case .game(let gid) = item.kind else { return false }
|
||||
guard let c = cityFromGameId(gid) else { return false }
|
||||
return cityMatches(c, searchCity: city)
|
||||
guard let gameCity = item.gameCity else { return false }
|
||||
return cityMatches(gameCity, searchCity: city)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +258,7 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
|
||||
days.append(dayData)
|
||||
}
|
||||
|
||||
return (days, travelValidRanges, itineraryItems + travelItems)
|
||||
return (days, travelValidRanges, gameItems + itineraryItems + travelItems)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
Reference in New Issue
Block a user