Skip to content

Commit 231a013

Browse files
[Bookings][Part 3] Booking details screen (#16191)
2 parents e830619 + ea110ea commit 231a013

File tree

7 files changed

+89
-8
lines changed

7 files changed

+89
-8
lines changed

Modules/Sources/Networking/Model/Bookings/Booking.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Foundation
33

44
/// Represents a Booking Entity.
55
///
6-
public struct Booking: Codable, GeneratedCopiable, Equatable, GeneratedFakeable {
6+
public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable {
77
public let siteID: Int64
88
public let bookingID: Int64
99
public let allDay: Bool

WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ struct BookingListContainerView: View {
3131
}
3232
}
3333
}
34+
.navigationDestination(for: Booking.self) { booking in
35+
let viewModel = BookingDetailsViewModel(booking: booking)
36+
BookingDetailsView(viewModel)
37+
}
3438
}
3539
}
3640

WooCommerce/Classes/Bookings/BookingList/BookingListView.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ private extension BookingListView {
3737
ScrollView {
3838
LazyVStack(spacing: 0) {
3939
ForEach(viewModel.bookings) { item in
40-
bookingItem(item)
40+
NavigationLink(value: item) {
41+
bookingItem(item)
42+
}
43+
.buttonStyle(.plain)
4144
}
4245

4346
InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator)

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import struct Networking.Booking
3+
import class WooFoundationCore.CurrencyFormatter
34

45
extension BookingDetailsViewModel {
56
struct AppointmentDetailsContent {
@@ -15,7 +16,6 @@ extension BookingDetailsViewModel {
1516
let rows: [Row]
1617

1718
init(_ booking: Booking) {
18-
let durationMinutes = Int(booking.endDate.timeIntervalSince(booking.startDate) / 60)
1919
let appointmentDate = booking.startDate.formatted(date: .numeric, time: .omitted)
2020
let appointmentTimeFrame = [
2121
booking.startDate.formatted(date: .omitted, time: .shortened),
@@ -27,13 +27,41 @@ extension BookingDetailsViewModel {
2727
Row(title: Localization.appointmentDetailsTimeRowTitle, value: appointmentTimeFrame),
2828
Row(title: Localization.appointmentDetailsAssignedStaffTitle, value: "Marianne Renoir"), /// Temporarily hardcoded
2929
Row(title: Localization.appointmentDetailsLocationTitle, value: "238 Willow Creek Drive, Montgomery ..."), /// Temporarily hardcoded
30-
Row(title: Localization.appointmentDetailsDurationTitle, value: String(durationMinutes)),
31-
Row(title: Localization.appointmentDetailsPriceTitle, value: booking.cost)
30+
Row(
31+
title: Localization.appointmentDetailsDurationTitle,
32+
value: Self.formatDuration(
33+
from: booking.startDate,
34+
to: booking.endDate
35+
)
36+
),
37+
Row(title: Localization.appointmentDetailsPriceTitle, value: Self.formatPrice(booking.cost))
3238
]
3339
}
3440
}
3541
}
3642

43+
private extension BookingDetailsViewModel.AppointmentDetailsContent {
44+
static let durationFormatter: DateComponentsFormatter = {
45+
let formatter = DateComponentsFormatter()
46+
formatter.unitsStyle = .short
47+
formatter.allowedUnits = [.hour, .minute]
48+
return formatter
49+
}()
50+
51+
static func formatDuration(from startDate: Date, to endDate: Date) -> String {
52+
durationFormatter.string(from: startDate, to: endDate) ?? ""
53+
}
54+
55+
static func formatPrice(_ price: String) -> String {
56+
guard let decimalPrice = Decimal(string: price) else {
57+
return price
58+
}
59+
return CurrencyFormatter(
60+
currencySettings: ServiceLocator.currencySettings
61+
).formatAmount(decimalPrice) ?? price
62+
}
63+
}
64+
3765
private extension BookingDetailsViewModel.AppointmentDetailsContent {
3866
enum Localization {
3967
static let appointmentDetailsDateRowTitle = NSLocalizedString(

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import struct Networking.Booking
33

44
final class BookingDetailsViewModel: ObservableObject {
55
let sections: [Section]
6+
let navigationTitle: String
67

78
init(booking: Booking) {
9+
navigationTitle = Self.navigationTitle(for: booking)
10+
811
let headerSection = Section.init(
912
content: .header(HeaderContent(booking))
1013
)
@@ -56,6 +59,17 @@ final class BookingDetailsViewModel: ObservableObject {
5659
}
5760
}
5861

62+
private extension BookingDetailsViewModel {
63+
static func navigationTitle(for booking: Booking) -> String {
64+
let titleFormat = NSLocalizedString(
65+
"BookingDetailsView.navTitle",
66+
value: "Booking #%1$d",
67+
comment: "Booking Details screen nav bar title. %1$d is a placeholder for the booking ID."
68+
)
69+
return String(format: titleFormat, booking.bookingID)
70+
}
71+
}
72+
5973
private extension BookingDetailsViewModel {
6074
enum Localization {
6175
static let appointmentDetailsSectionHeaderTitle = NSLocalizedString(

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import struct Networking.Booking
3+
import class WooFoundationCore.CurrencyFormatter
34

45
extension BookingDetailsViewModel {
56
struct PaymentContent {
@@ -8,10 +9,10 @@ extension BookingDetailsViewModel {
89

910
init(booking: Booking) {
1011
amounts = [
11-
.init(value: booking.cost, type: .service),
12-
.init(value: "$0", type: .tax),
12+
.init(value: Self.formatPrice(booking.cost), type: .service),
13+
.init(value: Self.formatPrice("0"), type: .tax),
1314
.init(value: "-", type: .discount),
14-
.init(value: "$55.00", type: .total, emphasized: true),
15+
.init(value: Self.formatPrice(booking.cost), type: .total, emphasized: true),
1516
]
1617

1718
actions = [
@@ -22,6 +23,17 @@ extension BookingDetailsViewModel {
2223
}
2324
}
2425

26+
private extension BookingDetailsViewModel.PaymentContent {
27+
static func formatPrice(_ price: String) -> String {
28+
guard let decimalPrice = Decimal(string: price) else {
29+
return price
30+
}
31+
return CurrencyFormatter(
32+
currencySettings: ServiceLocator.currencySettings
33+
).formatAmount(decimalPrice) ?? price
34+
}
35+
}
36+
2537
extension BookingDetailsViewModel.PaymentContent {
2638
struct Amount {
2739
enum AmountType {

WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Networking
33

44
struct BookingDetailsView: View {
55
@Environment(\.safeAreaInsets) var safeAreaInsets: EdgeInsets
6+
@Environment(\.dismiss) private var dismiss
67

78
@ObservedObject private var viewModel: BookingDetailsViewModel
89

@@ -37,7 +38,26 @@ struct BookingDetailsView: View {
3738
print("Refresh triggered")
3839
}
3940
.navigationBarTitleDisplayMode(.inline)
41+
.navigationTitle(viewModel.navigationTitle)
4042
.background(Color(uiColor: .systemGroupedBackground))
43+
.navigationBarBackButtonHidden(true)
44+
.toolbar {
45+
ToolbarItem(placement: .navigationBarLeading) {
46+
Button {
47+
dismiss()
48+
} label: {
49+
Image(systemName: "chevron.backward")
50+
}
51+
}
52+
ToolbarItem(placement: .navigationBarTrailing) {
53+
Button {
54+
//TODO: - present an action sheet
55+
print("On ellipsis item tap")
56+
} label: {
57+
Image(systemName: "ellipsis")
58+
}
59+
}
60+
}
4161
}
4262
}
4363

0 commit comments

Comments
 (0)