feat: add unified ItineraryItem model

Replaces CustomItineraryItem and TravelDayOverride with single model.
Supports game, travel, and custom item kinds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-17 20:57:56 -06:00
parent 49aaba0594
commit d14976812a

View File

@@ -0,0 +1,124 @@
import Foundation
import CoreLocation
/// Unified model for all itinerary items (games, travel, custom)
struct ItineraryItem: Identifiable, Codable, Hashable {
let id: UUID
let tripId: UUID
var day: Int // 1-indexed day number
var sortOrder: Double // Position within day (fractional)
var kind: ItemKind
var modifiedAt: Date
init(
id: UUID = UUID(),
tripId: UUID,
day: Int,
sortOrder: Double,
kind: ItemKind,
modifiedAt: Date = Date()
) {
self.id = id
self.tripId = tripId
self.day = day
self.sortOrder = sortOrder
self.kind = kind
self.modifiedAt = modifiedAt
}
}
/// The type of itinerary item
enum ItemKind: Codable, Hashable {
case game(gameId: String)
case travel(TravelInfo)
case custom(CustomInfo)
}
/// Travel-specific information
struct TravelInfo: Codable, Hashable {
let fromCity: String
let toCity: String
var distanceMeters: Double?
var durationSeconds: Double?
var formattedDistance: String {
guard let meters = distanceMeters else { return "" }
let miles = meters / 1609.34
return String(format: "%.0f mi", miles)
}
var formattedDuration: String {
guard let seconds = durationSeconds else { return "" }
let hours = Int(seconds) / 3600
let minutes = (Int(seconds) % 3600) / 60
if hours > 0 {
return "\(hours)h \(minutes)m"
}
return "\(minutes)m"
}
}
/// Custom item information
struct CustomInfo: Codable, Hashable {
var title: String
var icon: String
var time: Date?
var latitude: Double?
var longitude: Double?
var address: String?
var coordinate: CLLocationCoordinate2D? {
guard let lat = latitude, let lon = longitude else { return nil }
return CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
var isMappable: Bool {
latitude != nil && longitude != nil
}
}
// MARK: - Convenience Properties
extension ItineraryItem {
var isGame: Bool {
if case .game = kind { return true }
return false
}
var isTravel: Bool {
if case .travel = kind { return true }
return false
}
var isCustom: Bool {
if case .custom = kind { return true }
return false
}
var travelInfo: TravelInfo? {
if case .travel(let info) = kind { return info }
return nil
}
var customInfo: CustomInfo? {
if case .custom(let info) = kind { return info }
return nil
}
var gameId: String? {
if case .game(let id) = kind { return id }
return nil
}
/// Display title for the item
var displayTitle: String {
switch kind {
case .game(let gameId):
return "Game: \(gameId)"
case .travel(let info):
return "\(info.fromCity)\(info.toCity)"
case .custom(let info):
return info.title
}
}
}