-
Notifications
You must be signed in to change notification settings - Fork 121
[Bookings] Customer data for Booking Details #16214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
RafaelKayumov
merged 13 commits into
trunk
from
WOOMOB-1426-load-and-apply-cusromer-data
Oct 9, 2025
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a55d195
Fetch and display customer content in booking details
RafaelKayumov 70eaee2
Trigger data sync on init
RafaelKayumov 98a2c9d
Load local customer for booking details
RafaelKayumov 24bafde
Rework customer details content view for proper divider rendering
RafaelKayumov dde7555
Copy email to pasteboard
RafaelKayumov 1dd7b07
Add test for customer content
RafaelKayumov c44fc42
Merge branch 'trunk' into WOOMOB-1426-load-and-apply-cusromer-data
RafaelKayumov 0d27940
Bring back Billing Address title
RafaelKayumov 00b2184
Delete unused import
RafaelKayumov 7f122e2
Remove customer section absence as sync condition
RafaelKayumov be1c572
Delete unnecessary imports
RafaelKayumov 396a379
Apple contentShape modifier for tappable to have the whole area inter…
RafaelKayumov 7c2634e
Use .notice view modifier and pass notice presented to booking detail…
RafaelKayumov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,25 @@ | ||
| import Foundation | ||
| import struct Networking.Booking | ||
| import Yosemite | ||
| import SwiftUI // Added for withAnimation | ||
|
|
||
| final class BookingDetailsViewModel: ObservableObject { | ||
| let sections: [Section] | ||
| let navigationTitle: String | ||
| private let stores: StoresManager | ||
|
|
||
| private let booking: Booking | ||
| private let customerContent = CustomerContent() | ||
|
|
||
| init(booking: Booking) { | ||
| self.booking = booking | ||
| let navigationTitle: String | ||
| @Published private(set) var sections: [Section] = [] | ||
|
|
||
| init(booking: Booking, stores: StoresManager = ServiceLocator.stores) { | ||
| self.booking = booking | ||
| self.stores = stores | ||
| navigationTitle = Self.navigationTitle(for: booking) | ||
| setupSections() | ||
| } | ||
|
|
||
| let headerSection = Section.init( | ||
| private func setupSections() { | ||
| let headerSection = Section( | ||
| content: .header(HeaderContent(booking)) | ||
| ) | ||
|
|
||
|
|
@@ -27,21 +34,6 @@ final class BookingDetailsViewModel: ObservableObject { | |
| content: .attendance(AttendanceContent()) | ||
| ) | ||
|
|
||
| let customerSection = Section( | ||
| header: .title(Localization.customerSectionHeaderTitle.uppercased()), | ||
| content: .customer( | ||
| /// Temporary hardcode | ||
| CustomerContent( | ||
| nameText: "Margarita Nikolaevna", | ||
| emailText: "[email protected]", | ||
| phoneText: "+1 742582943798", | ||
| billingAddressText: """ | ||
| 238 Willow Creek Drive Montgomery AL 36109 | ||
| """ | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| let paymentSection = Section( | ||
| header: .title(Localization.paymentSectionHeaderTitle.uppercased()), | ||
| content: .payment(PaymentContent(booking: booking)) | ||
|
|
@@ -55,14 +47,100 @@ final class BookingDetailsViewModel: ObservableObject { | |
| sections = [ | ||
| headerSection, | ||
| appointmentDetailsSection, | ||
| customerSection, | ||
| attendanceSection, | ||
| paymentSection, | ||
| bookingNotes | ||
| ] | ||
| } | ||
| } | ||
|
|
||
| // MARK: Local Data | ||
|
|
||
| extension BookingDetailsViewModel { | ||
| func loadLocalData() { | ||
| loadCustomerData() | ||
| } | ||
| } | ||
|
|
||
| private extension BookingDetailsViewModel { | ||
| func loadCustomerData() { | ||
| guard booking.customerID > 0 else { | ||
| return | ||
| } | ||
|
|
||
| let action = CustomerAction.loadCustomer(siteID: booking.siteID, customerID: booking.customerID) { [weak self] result in | ||
| guard let self = self else { return } | ||
| if case .success(let customer) = result { | ||
| self.updateCustomerSection(with: customer) | ||
| } | ||
| } | ||
| stores.dispatch(action) | ||
| } | ||
| } | ||
|
|
||
| // MARK: Syncing | ||
|
|
||
| extension BookingDetailsViewModel { | ||
| func syncData() async { | ||
| await syncCustomer() | ||
| } | ||
| } | ||
|
|
||
| private extension BookingDetailsViewModel { | ||
| func syncCustomer() async { | ||
| guard shouldSyncCustomer else { | ||
| return | ||
| } | ||
|
|
||
| do { | ||
| let fetchedCustomer = try await retrieveCustomer() | ||
| updateCustomerSection(with: fetchedCustomer) | ||
| } catch { | ||
| DDLogError("⛔️ Error synchronizing Customer for Booking: \(error)") | ||
| } | ||
| } | ||
|
|
||
| @MainActor | ||
| func retrieveCustomer() async throws -> Customer { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| let action = CustomerAction.retrieveCustomer( | ||
| siteID: booking.siteID, | ||
| customerID: booking.customerID | ||
| ) { result in | ||
| switch result { | ||
| case .success(let customer): | ||
| continuation.resume(returning: customer) | ||
| case .failure(let error): | ||
| continuation.resume(throwing: error) | ||
| } | ||
| } | ||
| stores.dispatch(action) | ||
| } | ||
| } | ||
|
|
||
| func updateCustomerSection(with customer: Customer) { | ||
| customerContent.update(with: customer) | ||
|
|
||
| // Avoid adding if it already exists | ||
| guard !sections.contains(where: { if case .customer = $0.content { return true } else { return false } }) else { | ||
| return | ||
| } | ||
|
|
||
| let customerSection = Section( | ||
| header: .title(Localization.customerSectionHeaderTitle.uppercased()), | ||
| content: .customer(customerContent) | ||
| ) | ||
| withAnimation { | ||
| sections.insert(customerSection, at: 2) | ||
| } | ||
| } | ||
|
|
||
| /// Returns true when the `customerID` is non-zero and customer section doesn't exist | ||
| var shouldSyncCustomer: Bool { | ||
| return booking.customerID > 0 | ||
| } | ||
| } | ||
|
|
||
| extension BookingDetailsViewModel { | ||
| var cancellationAlertMessage: String { | ||
| // Temporary hardcoded | ||
|
|
||
42 changes: 37 additions & 5 deletions
42
WooCommerce/Classes/ViewModels/Booking Details/CustomerContent.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,42 @@ | ||
| import Foundation | ||
| import Networking | ||
|
|
||
| extension BookingDetailsViewModel { | ||
| struct CustomerContent { | ||
| let nameText: String | ||
| let emailText: String | ||
| let phoneText: String | ||
| let billingAddressText: String? | ||
| final class CustomerContent: ObservableObject { | ||
| @Published var nameText: String? | ||
| @Published var emailText: String? | ||
| @Published var phoneText: String? | ||
| @Published var billingAddressText: String? | ||
|
|
||
| func update(with customer: Customer) { | ||
| let name = [ | ||
| customer.firstName, | ||
| customer.lastName | ||
| ] | ||
| .compactMap { $0 } | ||
| .filter { !$0.isEmpty } | ||
| .joined(separator: " ") | ||
|
|
||
| let billingAddress = customer.billing.flatMap(formatAddress) | ||
|
|
||
| nameText = name | ||
| emailText = customer.email | ||
| phoneText = customer.billing?.phone ?? "" | ||
| billingAddressText = billingAddress | ||
| } | ||
|
|
||
| private func formatAddress(_ address: Address) -> String { | ||
| [ | ||
| address.address1, | ||
| address.address2, | ||
| address.city, | ||
| address.state, | ||
| address.postcode, | ||
| address.country | ||
| ] | ||
| .compactMap { $0 } | ||
| .filter { !$0.isEmpty } | ||
| .joined(separator: "\n") | ||
| } | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
...mmerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView+RowTextStyle.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import SwiftUI | ||
|
|
||
| private extension BookingDetailsView { | ||
| struct RowTextStyle: ViewModifier { | ||
| func body(content: Content) -> some View { | ||
| content | ||
| .font(TextFont.bodyMedium) | ||
| .foregroundStyle(.primary) | ||
| .multilineTextAlignment(.leading) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension View { | ||
| func rowTextStyle() -> some View { | ||
| self.modifier(BookingDetailsView.RowTextStyle()) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While running the app with Xcode, I saw a warning for this update: "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates." Please consider marking
updateCustomerSectionwith@MainActor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in the other PR in 2fee1b9726fe4b46d0732e83134d29de90e860eb