Skip to content

Commit ad04076

Browse files
[Bookings] Bookings details part 8 - switch to BookingOrderInfo data (#16240)
2 parents a5f48e9 + 26a1d72 commit ad04076

File tree

12 files changed

+134
-153
lines changed

12 files changed

+134
-153
lines changed

Modules/Sources/Yosemite/Actions/CustomerAction.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,4 @@ public enum CustomerAction: Action {
100100
///- `siteID`: The site for which customers should be delete.
101101
///- `onCompletion`: Invoked when the operation finishes.
102102
case deleteAllCustomers(siteID: Int64, onCompletion: () -> Void)
103-
104-
/// Loads a customer for the specified `siteID` and `customerID` from storage.
105-
case loadCustomer(siteID: Int64, customerID: Int64, onCompletion: (Result<Customer, Error>) -> Void)
106103
}

Modules/Sources/Yosemite/Stores/CustomerStore.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ public final class CustomerStore: Store {
8080
synchronizeAllCustomers(siteID: siteID, pageNumber: pageNumber, pageSize: pageSize, onCompletion: onCompletion)
8181
case .deleteAllCustomers(siteID: let siteID, onCompletion: let onCompletion):
8282
deleteAllCustomers(from: siteID, onCompletion: onCompletion)
83-
case let .loadCustomer(siteID, customerID, onCompletion):
84-
loadCustomer(siteID: siteID, customerID: customerID, onCompletion: onCompletion)
8583
}
8684
}
8785

@@ -253,16 +251,6 @@ public final class CustomerStore: Store {
253251
}, completion: onCompletion, on: .main)
254252
}
255253

256-
private func loadCustomer(siteID: Int64, customerID: Int64, onCompletion: @escaping (Result<Networking.Customer, Error>) -> Void) {
257-
let customers = storageManager.viewStorage.loadCustomers(siteID: siteID, matching: [customerID])
258-
if let storageCustomer = customers.first {
259-
let customer = storageCustomer.toReadOnly()
260-
onCompletion(.success(customer))
261-
} else {
262-
onCompletion(.failure(CustomerStoreError.notFound))
263-
}
264-
}
265-
266254
/// Maps CustomerSearchResult to Customer objects
267255
///
268256
/// - Parameters:
@@ -441,9 +429,3 @@ private extension CustomerStore {
441429
storageCustomer.update(with: readOnlyCustomer)
442430
}
443431
}
444-
445-
// MARK: - Errors
446-
447-
enum CustomerStoreError: Error {
448-
case notFound
449-
}

WooCommerce/Classes/Extensions/Booking+Helpers.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Foundation
22
import struct Yosemite.Booking
33

44
extension Booking {
5-
65
var summaryText: String {
76
let productName = orderInfo?.productInfo?.name
87
let customerName: String = {

WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import Yosemite
33

44
extension BookingDetailsViewModel {
5-
struct AppointmentDetailsContent {
5+
final class AppointmentDetailsContent: ObservableObject {
66
struct Row: Identifiable {
77
let title: String
88
let value: String
@@ -12,9 +12,9 @@ extension BookingDetailsViewModel {
1212
}
1313
}
1414

15-
let rows: [Row]
15+
@Published private(set) var rows: [Row] = []
1616

17-
init(_ booking: Booking, resource: BookingResource?) {
17+
func update(with booking: Booking, resource: BookingResource?) {
1818
let appointmentDate = booking.startDate.toString(dateStyle: .short, timeStyle: .none, timeZone: BookingListTab.utcTimeZone)
1919
let appointmentTimeFrame = [
2020
booking.startDate.toString(dateStyle: .none, timeStyle: .short, timeZone: BookingListTab.utcTimeZone),

WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel+Status.swift

Lines changed: 0 additions & 54 deletions
This file was deleted.

WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,72 @@
11
import Foundation
22
import Yosemite
33
import protocol Storage.StorageManagerType
4+
import SwiftUI
45

56
final 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

141178
extension 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,

WooCommerce/Classes/ViewModels/Booking Details/CustomerContent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extension BookingDetailsViewModel {
88
@Published var phoneText: String?
99
@Published var billingAddressText: String?
1010

11-
init(billingAddress: Address) {
11+
func update(with billingAddress: Address) {
1212
nameText = billingAddress.fullName
1313
emailText = billingAddress.email ?? ""
1414
phoneText = billingAddress.phone ?? ""
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
import Foundation
22
import struct Yosemite.Booking
3+
import struct Yosemite.BookingProductInfo
34
import struct Yosemite.Customer
5+
import struct Yosemite.Address
46

57
extension BookingDetailsViewModel {
68
final class HeaderContent: ObservableObject {
7-
let bookingDate: String
8-
let status: [Status]
9+
@Published private(set) var bookingDate: String = ""
10+
@Published private(set) var status: [String] = []
11+
@Published private(set) var serviceAndCustomerLine: String = ""
912

10-
@Published var serviceAndCustomerLine: String
11-
12-
init(_ booking: Booking) {
13+
func update(with booking: Booking) {
1314
bookingDate = booking.startDate.toString(
1415
dateStyle: .short,
1516
timeStyle: .short,
1617
timeZone: BookingListTab.utcTimeZone
1718
)
18-
1919
serviceAndCustomerLine = booking.summaryText
20-
status = [.booked, .payAtLocation]
20+
21+
let bookingStatus = booking.bookingStatus
22+
status = [
23+
"Booked",
24+
booking.bookingStatus.localizedTitle
25+
]
2126
}
2227
}
2328
}

0 commit comments

Comments
 (0)