Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Modules/Sources/Yosemite/Actions/CustomerAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,4 @@ public enum CustomerAction: Action {
///- `siteID`: The site for which customers should be delete.
///- `onCompletion`: Invoked when the operation finishes.
case deleteAllCustomers(siteID: Int64, onCompletion: () -> Void)

/// Loads a customer for the specified `siteID` and `customerID` from storage.
case loadCustomer(siteID: Int64, customerID: Int64, onCompletion: (Result<Customer, Error>) -> Void)
}
18 changes: 0 additions & 18 deletions Modules/Sources/Yosemite/Stores/CustomerStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ public final class CustomerStore: Store {
synchronizeAllCustomers(siteID: siteID, pageNumber: pageNumber, pageSize: pageSize, onCompletion: onCompletion)
case .deleteAllCustomers(siteID: let siteID, onCompletion: let onCompletion):
deleteAllCustomers(from: siteID, onCompletion: onCompletion)
case let .loadCustomer(siteID, customerID, onCompletion):
loadCustomer(siteID: siteID, customerID: customerID, onCompletion: onCompletion)
}
}

Expand Down Expand Up @@ -253,16 +251,6 @@ public final class CustomerStore: Store {
}, completion: onCompletion, on: .main)
}

private func loadCustomer(siteID: Int64, customerID: Int64, onCompletion: @escaping (Result<Networking.Customer, Error>) -> Void) {
let customers = storageManager.viewStorage.loadCustomers(siteID: siteID, matching: [customerID])
if let storageCustomer = customers.first {
let customer = storageCustomer.toReadOnly()
onCompletion(.success(customer))
} else {
onCompletion(.failure(CustomerStoreError.notFound))
}
}

/// Maps CustomerSearchResult to Customer objects
///
/// - Parameters:
Expand Down Expand Up @@ -441,9 +429,3 @@ private extension CustomerStore {
storageCustomer.update(with: readOnlyCustomer)
}
}

// MARK: - Errors

enum CustomerStoreError: Error {
case notFound
}
1 change: 0 additions & 1 deletion WooCommerce/Classes/Extensions/Booking+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Foundation
import struct Yosemite.Booking

extension Booking {

var summaryText: String {
let productName = orderInfo?.productInfo?.name
let customerName: String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
import Yosemite

extension BookingDetailsViewModel {
struct AppointmentDetailsContent {
final class AppointmentDetailsContent: ObservableObject {
struct Row: Identifiable {
let title: String
let value: String
Expand All @@ -12,9 +12,9 @@ extension BookingDetailsViewModel {
}
}

let rows: [Row]
@Published private(set) var rows: [Row] = []

init(_ booking: Booking, resource: BookingResource?) {
func update(with booking: Booking, resource: BookingResource?) {
let appointmentDate = booking.startDate.toString(dateStyle: .short, timeStyle: .none, timeZone: BookingListTab.utcTimeZone)
let appointmentTimeFrame = [
booking.startDate.toString(dateStyle: .none, timeStyle: .short, timeZone: BookingListTab.utcTimeZone),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,64 +1,72 @@
import Foundation
import Yosemite
import protocol Storage.StorageManagerType
import SwiftUI

final class BookingDetailsViewModel: ObservableObject {
private let stores: StoresManager

private var booking: Booking
private var bookingResource: BookingResource?
private var booking: Booking {
didSet {
updateDisplayProperties(from: booking)
}
}

private let headerContent = HeaderContent()
private let customerContent = CustomerContent()
private let appointmentDetailsContent = AppointmentDetailsContent()
private let attendanceContent = AttendanceContent()
private let paymentContent = PaymentContent()

// EntityListener: Update / Deletion Notifications.
///
private lazy var entityListener: EntityListener<Booking> = {
return EntityListener(storageManager: ServiceLocator.storageManager, readOnlyEntity: booking)
}()

let navigationTitle: String
@Published private(set) var navigationTitle = ""
@Published private(set) var sections: [Section] = []

init(booking: Booking,
stores: StoresManager = ServiceLocator.stores,
storage: StorageManagerType = ServiceLocator.storageManager) {
self.booking = booking
self.stores = stores
self.bookingResource = storage.viewStorage.loadBookingResource(
siteID: booking.siteID,
resourceID: booking.resourceID
)?.toReadOnly()

navigationTitle = Self.navigationTitle(for: booking)
let resource = storage.viewStorage.loadBookingResource(siteID: booking.siteID, resourceID: booking.resourceID)?.toReadOnly()
self.bookingResource = resource
setupSections(with: booking, resource: resource)
setupSections()
configureEntityListener()

updateDisplayProperties(from: booking)
}
}

private func setupSections(with booking: Booking, resource: BookingResource?) {
let headerContent = HeaderContent(booking)
// MARK: Private

private extension BookingDetailsViewModel {
func setupSections() {
let headerSection = Section(
content: .header(headerContent)
)

let appointmentDetailsSection = Section(
header: .title(Localization.appointmentDetailsSectionHeaderTitle.uppercased()),
content: .appointmentDetails(AppointmentDetailsContent(booking, resource: resource))
content: .appointmentDetails(appointmentDetailsContent)
)

let customerSection: Section? = {
guard let billingAddress = booking.orderInfo?.customerInfo?.billingAddress else { return nil }
let customerContent = CustomerContent(billingAddress: billingAddress)
return Section(
header: .title(Localization.customerSectionHeaderTitle.uppercased()),
content: .customer(customerContent)
)
}()

let attendanceSection = Section(
header: .title(Localization.attendanceSectionHeaderTitle.uppercased()),
footerText: Localization.attendanceSectionFooterText,
content: .attendance(AttendanceContent())
content: .attendance(attendanceContent)
)

let paymentSection = Section(
header: .title(Localization.paymentSectionHeaderTitle.uppercased()),
content: .payment(PaymentContent(booking: booking))
content: .payment(paymentContent)
)

let bookingNotes = Section(
Expand All @@ -69,42 +77,67 @@ final class BookingDetailsViewModel: ObservableObject {
sections = [
headerSection,
appointmentDetailsSection,
customerSection,
attendanceSection,
paymentSection,
bookingNotes
].compactMap { $0 }
]
}
}

// MARK: Syncing
func updateDisplayProperties(from booking: Booking) {
navigationTitle = Self.navigationTitle(for: booking)

extension BookingDetailsViewModel {
func syncData() async {
if let resource = await fetchResource() {
self.bookingResource = resource // only update resource if fetching succeeds
if let billingAddress = booking.orderInfo?.customerInfo?.billingAddress, !billingAddress.isEmpty {
customerContent.update(with: billingAddress)
insertCustomerSectionIfAbsent()
}
headerContent.update(with: booking)
appointmentDetailsContent.update(with: booking, resource: bookingResource)
paymentContent.update(with: booking)
}

func insertCustomerSectionIfAbsent() {
// Avoid adding if it already exists
let customerSectionExists = sections.contains {
if case .customer = $0.content {
return true
}

return false
}

guard !customerSectionExists else {
return
}

let customerSection = Section(
header: .title(Localization.customerSectionHeaderTitle.uppercased()),
content: .customer(customerContent)
)
withAnimation {
sections.insert(customerSection, at: 2)
}
await syncBooking()
}
}

private extension BookingDetailsViewModel {
func configureEntityListener() {
entityListener.onUpsert = { [weak self] booking in
guard let self else { return }
self.booking = booking
self.setupSections(with: booking, resource: bookingResource)
}
}
}

func syncBooking() async {
do {
try await retrieveBooking()
} catch {
DDLogError("⛔️ Error synchronizing Customer for Booking: \(error)")
// MARK: Syncing

extension BookingDetailsViewModel {
func syncData() async {
if let resource = await fetchResource() {
self.bookingResource = resource // only update resource if fetching succeeds
}
await fetchBooking()
}
}

private extension BookingDetailsViewModel {
@MainActor
func fetchResource() async -> BookingResource? {
do {
Expand All @@ -125,25 +158,35 @@ private extension BookingDetailsViewModel {
}

@MainActor
func retrieveBooking() async throws {
try await withCheckedThrowingContinuation { continuation in
let action = BookingAction.synchronizeBooking(
siteID: booking.siteID,
bookingID: booking.bookingID
) { result in
continuation.resume(with: result)
func fetchBooking() async {
do {
try await withCheckedThrowingContinuation { continuation in
let action = BookingAction.synchronizeBooking(
siteID: booking.siteID,
bookingID: booking.bookingID
) { result in
continuation.resume(with: result)
}
stores.dispatch(action)
}
stores.dispatch(action)
} catch {
DDLogError("⛔️ Error synchronizing Booking: \(error)")
}
}
}

extension BookingDetailsViewModel {
var cancellationAlertMessage: String {
// Temporary hardcoded
//TODO: - replace with associated customer data
let productName = "Women's Haircut"
let customerName = "Margarita Nikolaevna"
let productName = booking.orderInfo?.productInfo?.name ?? ""

let customerName: String = {
guard let address = booking.orderInfo?.customerInfo?.billingAddress else {
return ""
}
return [address.firstName, address.lastName]
.compactMap { $0 }
.joined(separator: " ")
}()

let date = booking.startDate.formatted(
date: .long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extension BookingDetailsViewModel {
@Published var phoneText: String?
@Published var billingAddressText: String?

init(billingAddress: Address) {
func update(with billingAddress: Address) {
nameText = billingAddress.fullName
emailText = billingAddress.email ?? ""
phoneText = billingAddress.phone ?? ""
Expand Down
19 changes: 12 additions & 7 deletions WooCommerce/Classes/ViewModels/Booking Details/HeaderContent.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import Foundation
import struct Yosemite.Booking
import struct Yosemite.BookingProductInfo
import struct Yosemite.Customer
import struct Yosemite.Address

extension BookingDetailsViewModel {
final class HeaderContent: ObservableObject {
let bookingDate: String
let status: [Status]
@Published private(set) var bookingDate: String = ""
@Published private(set) var status: [String] = []
@Published private(set) var serviceAndCustomerLine: String = ""

@Published var serviceAndCustomerLine: String

init(_ booking: Booking) {
func update(with booking: Booking) {
bookingDate = booking.startDate.toString(
dateStyle: .short,
timeStyle: .short,
timeZone: BookingListTab.utcTimeZone
)

serviceAndCustomerLine = booking.summaryText
status = [.booked, .payAtLocation]

let bookingStatus = booking.bookingStatus
status = [
"Booked",
booking.bookingStatus.localizedTitle
]
}
}
}
Loading