refactor(itinerary): replace CustomItineraryItem with ItineraryItem across codebase

- Update CKModels.swift to remove deleted type references
- Migrate LocalCustomItem to LocalItineraryItem in SavedTrip.swift
- Update AppDelegate to handle subscription removal
- Refactor AddItemSheet to create ItineraryItem with CustomInfo
- Update ItineraryTableViewController and Wrapper for new model
- Refactor TripDetailView state, methods and callbacks
- Fix TripMapView to display custom items with new model structure

This completes the migration from the legacy CustomItineraryItem/TravelDayOverride
model to the unified ItineraryItem model with ItemKind enum.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-17 21:51:32 -06:00
parent b008af1c71
commit cd00384010
7 changed files with 273 additions and 354 deletions

View File

@@ -8,14 +8,43 @@
import SwiftUI
import MapKit
/// Category for custom itinerary items with emoji icons
enum ItemCategory: String, CaseIterable {
case restaurant
case attraction
case fuel
case hotel
case other
var icon: String {
switch self {
case .restaurant: return "\u{1F37D}" // 🍽
case .attraction: return "\u{1F3A2}" // 🎢
case .fuel: return "\u{26FD}" //
case .hotel: return "\u{1F3E8}" // 🏨
case .other: return "\u{1F4CC}" // 📌
}
}
var label: String {
switch self {
case .restaurant: return "Eat"
case .attraction: return "See"
case .fuel: return "Fuel"
case .hotel: return "Stay"
case .other: return "Other"
}
}
}
struct AddItemSheet: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
let tripId: UUID
let day: Int
let existingItem: CustomItineraryItem?
var onSave: (CustomItineraryItem) -> Void
let existingItem: ItineraryItem?
var onSave: (ItineraryItem) -> Void
// Entry mode
enum EntryMode: String, CaseIterable {
@@ -24,7 +53,7 @@ struct AddItemSheet: View {
}
@State private var entryMode: EntryMode = .searchPlaces
@State private var selectedCategory: CustomItineraryItem.ItemCategory = .restaurant
@State private var selectedCategory: ItemCategory = .restaurant
@State private var title: String = ""
@State private var isSaving = false
@@ -80,9 +109,10 @@ struct AddItemSheet: View {
}
}
.onAppear {
if let existing = existingItem {
selectedCategory = existing.category
title = existing.title
if let existing = existingItem, let info = existing.customInfo {
// Find matching category by icon, default to .other
selectedCategory = ItemCategory.allCases.first { $0.icon == info.icon } ?? .other
title = info.title
// If editing a mappable item, switch to custom mode
entryMode = .custom
}
@@ -231,7 +261,7 @@ struct AddItemSheet: View {
@ViewBuilder
private var categoryPicker: some View {
HStack(spacing: Theme.Spacing.md) {
ForEach(CustomItineraryItem.ItemCategory.allCases, id: \.self) { category in
ForEach(ItemCategory.allCases, id: \.self) { category in
CategoryButton(
category: category,
isSelected: selectedCategory == category
@@ -245,54 +275,68 @@ struct AddItemSheet: View {
private func saveItem() {
isSaving = true
let item: CustomItineraryItem
let item: ItineraryItem
if let existing = existingItem {
if let existing = existingItem, let existingInfo = existing.customInfo {
// Editing existing item - preserve day and sortOrder
let trimmedTitle = title.trimmingCharacters(in: .whitespaces)
guard !trimmedTitle.isEmpty else { return }
item = CustomItineraryItem(
let customInfo = CustomInfo(
title: trimmedTitle,
icon: selectedCategory.icon,
time: existingInfo.time,
latitude: existingInfo.latitude,
longitude: existingInfo.longitude,
address: existingInfo.address
)
item = ItineraryItem(
id: existing.id,
tripId: existing.tripId,
category: selectedCategory,
title: trimmedTitle,
day: existing.day,
sortOrder: existing.sortOrder,
createdAt: existing.createdAt,
modifiedAt: Date(),
latitude: existing.latitude,
longitude: existing.longitude,
address: existing.address
kind: .custom(customInfo),
modifiedAt: Date()
)
} else if entryMode == .searchPlaces, let place = selectedPlace {
// Creating from MapKit search
let placeName = place.name ?? "Unknown Place"
let coordinate = place.placemark.coordinate
// New items get sortOrder 0 (will be placed at beginning, caller can adjust)
item = CustomItineraryItem(
tripId: tripId,
category: selectedCategory,
let customInfo = CustomInfo(
title: placeName,
day: day,
sortOrder: 0.0,
icon: selectedCategory.icon,
time: nil,
latitude: coordinate.latitude,
longitude: coordinate.longitude,
address: formatAddress(for: place)
)
// New items get sortOrder 0 (will be placed at beginning, caller can adjust)
item = ItineraryItem(
tripId: tripId,
day: day,
sortOrder: 0.0,
kind: .custom(customInfo)
)
} else {
// Creating custom item (no location)
let trimmedTitle = title.trimmingCharacters(in: .whitespaces)
guard !trimmedTitle.isEmpty else { return }
// New items get sortOrder 0 (will be placed at beginning, caller can adjust)
item = CustomItineraryItem(
tripId: tripId,
category: selectedCategory,
let customInfo = CustomInfo(
title: trimmedTitle,
icon: selectedCategory.icon,
time: nil
)
// New items get sortOrder 0 (will be placed at beginning, caller can adjust)
item = ItineraryItem(
tripId: tripId,
day: day,
sortOrder: 0.0
sortOrder: 0.0,
kind: .custom(customInfo)
)
}
@@ -353,7 +397,7 @@ private struct PlaceResultRow: View {
// MARK: - Category Button
private struct CategoryButton: View {
let category: CustomItineraryItem.ItemCategory
let category: ItemCategory
let isSelected: Bool
let action: () -> Void