feat(itinerary): add custom itinerary items with drag-to-reorder

- Add CustomItineraryItem domain model with sortOrder for ordering
- Add CKCustomItineraryItem CloudKit wrapper for persistence
- Create CustomItemService for CRUD operations
- Create CustomItemSubscriptionService for real-time sync
- Add AppDelegate for push notification handling
- Add AddItemSheet for creating/editing items
- Add CustomItemRow with drag handle
- Update TripDetailView with continuous vertical timeline
- Enable drag-to-reorder using .draggable/.dropDestination
- Add inline "Add" buttons after games and travel segments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-16 00:31:44 -06:00
parent b534ca771b
commit 495ef88303
13 changed files with 1302 additions and 33 deletions

View File

@@ -101,6 +101,79 @@ final class TripVote {
}
}
// MARK: - Local Custom Item (Cache)
@Model
final class LocalCustomItem {
@Attribute(.unique) var id: UUID
var tripId: UUID
var category: String
var title: String
var anchorType: String
var anchorId: String?
var anchorDay: Int
var createdAt: Date
var modifiedAt: Date
var pendingSync: Bool // True if needs to sync to CloudKit
init(
id: UUID = UUID(),
tripId: UUID,
category: CustomItineraryItem.ItemCategory,
title: String,
anchorType: CustomItineraryItem.AnchorType = .startOfDay,
anchorId: String? = nil,
anchorDay: Int,
createdAt: Date = Date(),
modifiedAt: Date = Date(),
pendingSync: Bool = false
) {
self.id = id
self.tripId = tripId
self.category = category.rawValue
self.title = title
self.anchorType = anchorType.rawValue
self.anchorId = anchorId
self.anchorDay = anchorDay
self.createdAt = createdAt
self.modifiedAt = modifiedAt
self.pendingSync = pendingSync
}
var toItem: CustomItineraryItem? {
guard let category = CustomItineraryItem.ItemCategory(rawValue: category),
let anchorType = CustomItineraryItem.AnchorType(rawValue: anchorType)
else { return nil }
return CustomItineraryItem(
id: id,
tripId: tripId,
category: category,
title: title,
anchorType: anchorType,
anchorId: anchorId,
anchorDay: anchorDay,
createdAt: createdAt,
modifiedAt: modifiedAt
)
}
static func from(_ item: CustomItineraryItem, pendingSync: Bool = false) -> LocalCustomItem {
LocalCustomItem(
id: item.id,
tripId: item.tripId,
category: item.category,
title: item.title,
anchorType: item.anchorType,
anchorId: item.anchorId,
anchorDay: item.anchorDay,
createdAt: item.createdAt,
modifiedAt: item.modifiedAt,
pendingSync: pendingSync
)
}
}
// MARK: - User Preferences
@Model