fix(pdf): include all trip days in PDF export

Fixed PDF export missing the last day when games occur on departure date.

Root cause: Trip.itineraryDays() calculated lastActivityDate as departure - 1,
assuming departure is always after the last activity. When games happen ON the
departure date, that day was skipped.

Fix: Check if the last stop has games. If so, include the departure date in
the itinerary loop.

Also includes:
- buildCompleteItineraryItems() to merge games, travel, and custom items
- Magazine-style PDF layout with sport-specific accent colors
- Proper iteration over all trip days in PDF generator

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-19 11:47:25 -06:00
parent 239d22a872
commit 0e7fcb65fc
3 changed files with 540 additions and 822 deletions

View File

@@ -1199,7 +1199,14 @@ struct TripDetailView: View {
exportProgress = nil
do {
let url = try await exportService.exportToPDF(trip: trip, games: games) { progress in
// Build complete itinerary items (games + travel + custom)
let completeItems = buildCompleteItineraryItems()
let url = try await exportService.exportToPDF(
trip: trip,
games: games,
itineraryItems: completeItems
) { progress in
await MainActor.run {
self.exportProgress = progress
}
@@ -1213,6 +1220,57 @@ struct TripDetailView: View {
isExporting = false
}
/// Build complete itinerary items by merging games, travel, and custom items
private func buildCompleteItineraryItems() -> [ItineraryItem] {
var allItems: [ItineraryItem] = []
// Get itinerary days from trip
let tripDays = trip.itineraryDays()
// 1. Add game items using day.gameIds (reliable source from trip stops)
for day in tripDays {
for (gameIndex, gameId) in day.gameIds.enumerated() {
guard let richGame = games[gameId] else { continue }
let gameItem = ItineraryItem(
tripId: trip.id,
day: day.dayNumber,
sortOrder: Double(gameIndex) * 0.01, // Games near the start of the day
kind: .game(gameId: gameId, city: richGame.stadium.city)
)
allItems.append(gameItem)
}
}
// 2. Add travel items (from trip segments + overrides)
for segment in trip.travelSegments {
let travelId = "travel:\(segment.fromLocation.name.lowercased())->\(segment.toLocation.name.lowercased())"
// Use override if available, otherwise default to day 1
let override = travelOverrides[travelId]
let day = override?.day ?? 1
let sortOrder = override?.sortOrder ?? 100.0 // After games by default
let travelItem = ItineraryItem(
tripId: trip.id,
day: day,
sortOrder: sortOrder,
kind: .travel(TravelInfo(
fromCity: segment.fromLocation.name,
toCity: segment.toLocation.name,
distanceMeters: segment.distanceMeters,
durationSeconds: segment.durationSeconds
))
)
allItems.append(travelItem)
}
// 3. Add custom items (from CloudKit)
let customItems = itineraryItems.filter { $0.isCustom }
allItems.append(contentsOf: customItems)
return allItems
}
private func toggleSaved() {
if isSaved {
unsaveTrip()