refactor(itinerary): replace anchor-based positioning with day/sortOrder

Replace complex anchor system (anchorType, anchorId, anchorDay) with
simple (day: Int, sortOrder: Double) positioning for custom items.

Changes:
- CustomItineraryItem: Remove anchor fields, add day and sortOrder
- CKModels: Add migration fallback from old CloudKit fields
- ItineraryTableViewController: Add calculateSortOrder() for midpoint insertion
- TripDetailView: Simplify callbacks, itinerarySections, and routeWaypoints
- AddItemSheet: Take simple day parameter instead of anchor
- SavedTrip: Update LocalCustomItem SwiftData model

Benefits:
- Items freely movable via drag-and-drop
- Route waypoints follow exact visual order
- Simpler mental model: position = (day, sortOrder)
- Midpoint insertion allows unlimited reordering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-17 09:47:11 -06:00
parent 59ba2c6965
commit 2a8bfeeff8
10 changed files with 238 additions and 385 deletions

View File

@@ -18,10 +18,10 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
// Callbacks
var onTravelMoved: ((String, Int) -> Void)?
var onCustomItemMoved: ((UUID, Int, CustomItineraryItem.AnchorType, String?) -> Void)?
var onCustomItemMoved: ((UUID, Int, Double) -> Void)? // itemId, newDay, newSortOrder
var onCustomItemTapped: ((CustomItineraryItem) -> Void)?
var onCustomItemDeleted: ((CustomItineraryItem) -> Void)?
var onAddButtonTapped: ((Int, CustomItineraryItem.AnchorType, String?) -> Void)?
var onAddButtonTapped: ((Int) -> Void)? // Just day number
init(
trip: Trip,
@@ -30,10 +30,10 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
travelDayOverrides: [String: Int],
@ViewBuilder headerContent: () -> HeaderContent,
onTravelMoved: ((String, Int) -> Void)? = nil,
onCustomItemMoved: ((UUID, Int, CustomItineraryItem.AnchorType, String?) -> Void)? = nil,
onCustomItemMoved: ((UUID, Int, Double) -> Void)? = nil,
onCustomItemTapped: ((CustomItineraryItem) -> Void)? = nil,
onCustomItemDeleted: ((CustomItineraryItem) -> Void)? = nil,
onAddButtonTapped: ((Int, CustomItineraryItem.AnchorType, String?) -> Void)? = nil
onAddButtonTapped: ((Int) -> Void)? = nil
) {
self.trip = trip
self.games = games
@@ -159,47 +159,16 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
// Travel before this day (travel is stored on the destination day)
let travelBefore: TravelSegment? = travelByDay[dayNum]
// Custom items after travel (if travel arrives on this day)
if let travelSegment = travelBefore {
let travelId = stableTravelAnchorId(travelSegment)
let itemsAfterTravel = customItems.filter {
$0.anchorType == .afterTravel && $0.anchorId == travelId
}.sorted { $0.sortOrder < $1.sortOrder }
// Custom items for this day - simply filter by day and sort by sortOrder
let dayItems = customItems.filter { $0.day == dayNum }
.sorted { $0.sortOrder < $1.sortOrder }
for item in itemsAfterTravel {
items.append(ItineraryRowItem.customItem(item))
}
}
// Custom items at start of day
let itemsAtStart = customItems.filter {
$0.anchorDay == dayNum && $0.anchorType == .startOfDay
}.sorted { $0.sortOrder < $1.sortOrder }
for item in itemsAtStart {
for item in dayItems {
items.append(ItineraryRowItem.customItem(item))
}
// Custom items after game
if let lastGame = gamesOnDay.last {
let itemsAfterGame = customItems.filter {
$0.anchorDay == dayNum && $0.anchorType == .afterGame && $0.anchorId == lastGame.game.id
}.sorted { $0.sortOrder < $1.sortOrder }
for item in itemsAfterGame {
items.append(ItineraryRowItem.customItem(item))
}
}
// ONE Add button per day - after the last thing (game > travel > rest day)
if let lastGame = gamesOnDay.last {
items.append(ItineraryRowItem.addButton(day: dayNum, anchorType: .afterGame, anchorId: lastGame.game.id))
} else if let travelSegment = travelBefore {
let travelId = stableTravelAnchorId(travelSegment)
items.append(ItineraryRowItem.addButton(day: dayNum, anchorType: .afterTravel, anchorId: travelId))
} else {
items.append(ItineraryRowItem.addButton(day: dayNum, anchorType: .startOfDay, anchorId: nil))
}
// ONE Add button per day
items.append(ItineraryRowItem.addButton(day: dayNum))
let dayData = ItineraryDayData(
id: dayNum,