11import Foundation
22import Yosemite
33import protocol Storage. StorageManagerType
4+ import SwiftUI
45
56final class BookingDetailsViewModel : ObservableObject {
67 private let stores : StoresManager
78
8- private var booking : Booking
99 private var bookingResource : BookingResource ?
10+ private var booking : Booking {
11+ didSet {
12+ updateDisplayProperties ( from: booking)
13+ }
14+ }
15+
16+ private let headerContent = HeaderContent ( )
17+ private let customerContent = CustomerContent ( )
18+ private let appointmentDetailsContent = AppointmentDetailsContent ( )
19+ private let attendanceContent = AttendanceContent ( )
20+ private let paymentContent = PaymentContent ( )
1021
1122 // EntityListener: Update / Deletion Notifications.
1223 ///
1324 private lazy var entityListener : EntityListener < Booking > = {
1425 return EntityListener ( storageManager: ServiceLocator . storageManager, readOnlyEntity: booking)
1526 } ( )
1627
17- let navigationTitle : String
28+ @ Published private ( set ) var navigationTitle = " "
1829 @Published private( set) var sections : [ Section ] = [ ]
1930
2031 init ( booking: Booking ,
2132 stores: StoresManager = ServiceLocator . stores,
2233 storage: StorageManagerType = ServiceLocator . storageManager) {
2334 self . booking = booking
2435 self . stores = stores
36+ self . bookingResource = storage. viewStorage. loadBookingResource (
37+ siteID: booking. siteID,
38+ resourceID: booking. resourceID
39+ ) ? . toReadOnly ( )
2540
26- navigationTitle = Self . navigationTitle ( for: booking)
27- let resource = storage. viewStorage. loadBookingResource ( siteID: booking. siteID, resourceID: booking. resourceID) ? . toReadOnly ( )
28- self . bookingResource = resource
29- setupSections ( with: booking, resource: resource)
41+ setupSections ( )
3042 configureEntityListener ( )
43+
44+ updateDisplayProperties ( from: booking)
3145 }
46+ }
3247
33- private func setupSections( with booking: Booking , resource: BookingResource ? ) {
34- let headerContent = HeaderContent ( booking)
48+ // MARK: Private
49+
50+ private extension BookingDetailsViewModel {
51+ func setupSections( ) {
3552 let headerSection = Section (
3653 content: . header( headerContent)
3754 )
3855
3956 let appointmentDetailsSection = Section (
4057 header: . title( Localization . appointmentDetailsSectionHeaderTitle. uppercased ( ) ) ,
41- content: . appointmentDetails( AppointmentDetailsContent ( booking , resource : resource ) )
58+ content: . appointmentDetails( appointmentDetailsContent )
4259 )
4360
44- let customerSection : Section ? = {
45- guard let billingAddress = booking. orderInfo? . customerInfo? . billingAddress else { return nil }
46- let customerContent = CustomerContent ( billingAddress: billingAddress)
47- return Section (
48- header: . title( Localization . customerSectionHeaderTitle. uppercased ( ) ) ,
49- content: . customer( customerContent)
50- )
51- } ( )
52-
5361 let attendanceSection = Section (
5462 header: . title( Localization . attendanceSectionHeaderTitle. uppercased ( ) ) ,
5563 footerText: Localization . attendanceSectionFooterText,
56- content: . attendance( AttendanceContent ( ) )
64+ content: . attendance( attendanceContent )
5765 )
5866
5967 let paymentSection = Section (
6068 header: . title( Localization . paymentSectionHeaderTitle. uppercased ( ) ) ,
61- content: . payment( PaymentContent ( booking : booking ) )
69+ content: . payment( paymentContent )
6270 )
6371
6472 let bookingNotes = Section (
@@ -69,42 +77,67 @@ final class BookingDetailsViewModel: ObservableObject {
6977 sections = [
7078 headerSection,
7179 appointmentDetailsSection,
72- customerSection,
7380 attendanceSection,
7481 paymentSection,
7582 bookingNotes
76- ] . compactMap { $0 }
83+ ]
7784 }
78- }
7985
80- // MARK: Syncing
86+ func updateDisplayProperties( from booking: Booking ) {
87+ navigationTitle = Self . navigationTitle ( for: booking)
8188
82- extension BookingDetailsViewModel {
83- func syncData( ) async {
84- if let resource = await fetchResource ( ) {
85- self . bookingResource = resource // only update resource if fetching succeeds
89+ if let billingAddress = booking. orderInfo? . customerInfo? . billingAddress, !billingAddress. isEmpty {
90+ customerContent. update ( with: billingAddress)
91+ insertCustomerSectionIfAbsent ( )
92+ }
93+ headerContent. update ( with: booking)
94+ appointmentDetailsContent. update ( with: booking, resource: bookingResource)
95+ paymentContent. update ( with: booking)
96+ }
97+
98+ func insertCustomerSectionIfAbsent( ) {
99+ // Avoid adding if it already exists
100+ let customerSectionExists = sections. contains {
101+ if case . customer = $0. content {
102+ return true
103+ }
104+
105+ return false
106+ }
107+
108+ guard !customerSectionExists else {
109+ return
110+ }
111+
112+ let customerSection = Section (
113+ header: . title( Localization . customerSectionHeaderTitle. uppercased ( ) ) ,
114+ content: . customer( customerContent)
115+ )
116+ withAnimation {
117+ sections. insert ( customerSection, at: 2 )
86118 }
87- await syncBooking ( )
88119 }
89- }
90120
91- private extension BookingDetailsViewModel {
92121 func configureEntityListener( ) {
93122 entityListener. onUpsert = { [ weak self] booking in
94123 guard let self else { return }
95124 self . booking = booking
96- self . setupSections ( with: booking, resource: bookingResource)
97125 }
98126 }
127+ }
99128
100- func syncBooking( ) async {
101- do {
102- try await retrieveBooking ( )
103- } catch {
104- DDLogError ( " ⛔️ Error synchronizing Customer for Booking: \( error) " )
129+ // MARK: Syncing
130+
131+ extension BookingDetailsViewModel {
132+ func syncData( ) async {
133+ if let resource = await fetchResource ( ) {
134+ self . bookingResource = resource // only update resource if fetching succeeds
105135 }
136+ await fetchBooking ( )
106137 }
138+ }
107139
140+ private extension BookingDetailsViewModel {
108141 @MainActor
109142 func fetchResource( ) async -> BookingResource ? {
110143 do {
@@ -125,25 +158,35 @@ private extension BookingDetailsViewModel {
125158 }
126159
127160 @MainActor
128- func retrieveBooking( ) async throws {
129- try await withCheckedThrowingContinuation { continuation in
130- let action = BookingAction . synchronizeBooking (
131- siteID: booking. siteID,
132- bookingID: booking. bookingID
133- ) { result in
134- continuation. resume ( with: result)
161+ func fetchBooking( ) async {
162+ do {
163+ try await withCheckedThrowingContinuation { continuation in
164+ let action = BookingAction . synchronizeBooking (
165+ siteID: booking. siteID,
166+ bookingID: booking. bookingID
167+ ) { result in
168+ continuation. resume ( with: result)
169+ }
170+ stores. dispatch ( action)
135171 }
136- stores. dispatch ( action)
172+ } catch {
173+ DDLogError ( " ⛔️ Error synchronizing Booking: \( error) " )
137174 }
138175 }
139176}
140177
141178extension BookingDetailsViewModel {
142179 var cancellationAlertMessage : String {
143- // Temporary hardcoded
144- //TODO: - replace with associated customer data
145- let productName = " Women's Haircut "
146- let customerName = " Margarita Nikolaevna "
180+ let productName = booking. orderInfo? . productInfo? . name ?? " "
181+
182+ let customerName : String = {
183+ guard let address = booking. orderInfo? . customerInfo? . billingAddress else {
184+ return " "
185+ }
186+ return [ address. firstName, address. lastName]
187+ . compactMap { $0 }
188+ . joined ( separator: " " )
189+ } ( )
147190
148191 let date = booking. startDate. formatted (
149192 date: . long,
0 commit comments