Skip to content

Commit f3d19d1

Browse files
[Bookings] Apply attendance status to UI (#16260)
2 parents b6a03c3 + 568a376 commit f3d19d1

File tree

15 files changed

+284
-30
lines changed

15 files changed

+284
-30
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable {
2929
return BookingStatus(rawValue: statusKey) ?? .unknown
3030
}
3131

32-
/// periphery: ignore - will be used in UI in upcoming PRs
3332
public var attendanceStatus: BookingAttendanceStatus {
3433
return BookingAttendanceStatus(rawValue: attendanceStatusKey) ?? .unknown
3534
}
@@ -208,7 +207,6 @@ public enum BookingStatus: String, CaseIterable {
208207
case unknown
209208
}
210209

211-
/// periphery: ignore - will be used in UI in upcoming PRs
212210
public enum BookingAttendanceStatus: String, CaseIterable {
213211
case booked
214212
case checkedIn = "checked-in"

Modules/Sources/Networking/Remote/BookingsRemote.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ public protocol BookingsRemoteProtocol {
1717
func loadBooking(bookingID: Int64,
1818
siteID: Int64) async throws -> Booking?
1919

20+
func updateBooking(
21+
from siteID: Int64,
22+
bookingID: Int64,
23+
attendanceStatus: BookingAttendanceStatus
24+
) async throws -> Booking?
25+
2026
func fetchResource(resourceID: Int64,
2127
siteID: Int64) async throws -> BookingResource?
2228
}
@@ -88,6 +94,28 @@ public final class BookingsRemote: Remote, BookingsRemoteProtocol {
8894
return try await enqueue(request, mapper: mapper)
8995
}
9096

97+
public func updateBooking(
98+
from siteID: Int64,
99+
bookingID: Int64,
100+
attendanceStatus: BookingAttendanceStatus
101+
) async throws -> Booking? {
102+
let path = "\(Path.bookings)/\(bookingID)"
103+
let parameters = [
104+
ParameterKey.attendanceStatus: attendanceStatus.rawValue
105+
]
106+
let request = JetpackRequest(
107+
wooApiVersion: .wcBookings,
108+
method: .put,
109+
siteID: siteID,
110+
path: path,
111+
parameters: parameters,
112+
availableAsRESTRequest: true
113+
)
114+
115+
let mapper = BookingMapper(siteID: siteID)
116+
return try await enqueue(request, mapper: mapper)
117+
}
118+
91119
public func fetchResource(
92120
resourceID: Int64,
93121
siteID: Int64
@@ -132,5 +160,6 @@ public extension BookingsRemote {
132160
static let startDateAfter: String = "start_date_after"
133161
static let search: String = "search"
134162
static let order: String = "order"
163+
static let attendanceStatus = "attendance_status"
135164
}
136165
}

Modules/Sources/Yosemite/Actions/BookingAction.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,16 @@ public enum BookingAction: Action {
5252
case fetchResource(siteID: Int64,
5353
resourceID: Int64,
5454
onCompletion: (Result<BookingResource, Error>) -> Void)
55+
56+
/// Updates a booking attendance status.
57+
///
58+
/// - Parameter siteID: The site ID of the booking.
59+
/// - Parameter bookingID: The ID of the booking to be updated.
60+
/// - Parameter status: The new attendance status.
61+
/// - Parameter onCompletion: called when update completes, returns an error in case of a failure.
62+
///
63+
case updateBookingAttendanceStatus(siteID: Int64,
64+
bookingID: Int64,
65+
status: BookingAttendanceStatus,
66+
onCompletion: (Error?) -> Void)
5567
}

Modules/Sources/Yosemite/Model/Model.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public typealias BookingCustomerInfo = Networking.BookingCustomerInfo
3131
public typealias BookingPaymentInfo = Networking.BookingPaymentInfo
3232
public typealias BookingProductInfo = Networking.BookingProductInfo
3333
public typealias BookingResource = Networking.BookingResource
34+
public typealias BookingAttendanceStatus = Networking.BookingAttendanceStatus
3435
public typealias CreateBlazeCampaign = Networking.CreateBlazeCampaign
3536
public typealias FallibleCancelable = Hardware.FallibleCancelable
3637
public typealias CommentStatus = Networking.CommentStatus

Modules/Sources/Yosemite/Stores/BookingStore.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,17 @@ public class BookingStore: Store {
6363
onCompletion: onCompletion)
6464
case let .fetchResource(siteID, resourceID, onCompletion):
6565
fetchResource(siteID: siteID, resourceID: resourceID, onCompletion: onCompletion)
66+
case .updateBookingAttendanceStatus(let siteID, let bookingID, let status, let onCompletion):
67+
performUpdateBookingAttendanceStatus(
68+
siteID: siteID,
69+
bookingID: bookingID,
70+
status: status,
71+
onCompletion: onCompletion
72+
)
6673
}
6774
}
6875
}
6976

70-
7177
// MARK: - Services
7278
//
7379
private extension BookingStore {
@@ -246,6 +252,50 @@ private extension BookingStore {
246252
}
247253
}
248254
}
255+
256+
func performUpdateBookingAttendanceStatus(
257+
siteID: Int64,
258+
bookingID: Int64,
259+
status: BookingAttendanceStatus,
260+
onCompletion: @escaping (Error?) -> Void
261+
) {
262+
updateBookingAttendanceStatusLocally(
263+
siteID: siteID,
264+
bookingID: bookingID,
265+
statusKey: status
266+
) { _ in
267+
//TODO: - booking status remote update + rollback status in case of error
268+
onCompletion(nil)
269+
}
270+
}
271+
272+
/// Updates local (Storage) Booking attendance status
273+
func updateBookingAttendanceStatusLocally(
274+
siteID: Int64,
275+
bookingID: Int64,
276+
statusKey: BookingAttendanceStatus,
277+
onCompletion: @escaping (BookingAttendanceStatus) -> Void
278+
) {
279+
storageManager.performAndSave({ storage -> BookingAttendanceStatus in
280+
guard let booking = storage.loadBooking(
281+
siteID: siteID,
282+
bookingID: bookingID
283+
) else {
284+
return statusKey
285+
}
286+
287+
let oldStatus = booking.attendanceStatusKey
288+
booking.attendanceStatusKey = statusKey.rawValue
289+
return BookingAttendanceStatus(rawValue: oldStatus ?? "") ?? .unknown
290+
}, completion: { result in
291+
switch result {
292+
case .success(let status):
293+
onCompletion(status)
294+
case .failure:
295+
onCompletion(statusKey)
296+
}
297+
}, on: .main)
298+
}
249299
}
250300

251301

Modules/Tests/YosemiteTests/Mocks/MockBookingsRemote.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,8 @@ final class MockBookingsRemote: BookingsRemoteProtocol {
4646
}
4747
return try result.get()
4848
}
49+
50+
func updateBooking(from siteID: Int64, bookingID: Int64, attendanceStatus: Networking.BookingAttendanceStatus) async throws -> Networking.Booking? {
51+
return nil
52+
}
4953
}

WooCommerce/Classes/Bookings/BookingList/BookingListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private extension BookingListView {
136136
HStack {
137137
// TODO: update this when attendance status is available
138138
// Update badge colors if design changes as statuses are not clarified now.
139-
statusBadge(text: "Booked", color: Layout.defaultBadgeColor)
139+
statusBadge(text: booking.attendanceStatus.localizedTitle, color: Layout.defaultBadgeColor)
140140
statusBadge(text: booking.bookingStatus.localizedTitle, color: Layout.defaultBadgeColor)
141141
Spacer()
142142
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import Foundation
2+
import Networking
23

34
extension BookingDetailsViewModel {
4-
struct AttendanceContent {
5-
/// Hardcoded attendance value
6-
/// Will be replaced with model value or binding
7-
let value = "Booked"
5+
final class AttendanceContent {
6+
private(set) var value = ""
7+
8+
func update(with booking: Booking) {
9+
value = booking.attendanceStatus.localizedTitle
10+
}
811
}
912
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
import Networking
3+
4+
extension BookingAttendanceStatus {
5+
var localizedTitle: String {
6+
switch self {
7+
case .booked:
8+
return NSLocalizedString(
9+
"BookingAttendanceStatus.booked",
10+
value: "Booked",
11+
comment: "Title for 'Booked' booking attendance status."
12+
)
13+
case .checkedIn:
14+
return NSLocalizedString(
15+
"BookingAttendanceStatus.checkedIn",
16+
value: "Checked In",
17+
comment: "Title for 'Checked In' booking attendance status."
18+
)
19+
case .cancelled:
20+
return NSLocalizedString(
21+
"BookingAttendanceStatus.cancelled",
22+
value: "Cancelled",
23+
comment: "Title for 'Cancelled' booking attendance status."
24+
)
25+
case .noShow:
26+
return NSLocalizedString(
27+
"BookingAttendanceStatus.noShow",
28+
value: "No Show",
29+
comment: "Title for 'No Show' booking attendance status."
30+
)
31+
case .unknown:
32+
return NSLocalizedString(
33+
"BookingAttendanceStatus.unknown",
34+
value: "Unknown",
35+
comment: "Title for 'Unknown' booking attendance status."
36+
)
37+
}
38+
}
39+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ final class BookingDetailsViewModel: ObservableObject {
2828
@Published private(set) var navigationTitle = ""
2929
@Published private(set) var sections: [Section] = []
3030

31+
var bookingAttendanceStatus: BookingAttendanceStatus {
32+
booking.attendanceStatus
33+
}
34+
3135
init(booking: Booking,
3236
stores: StoresManager = ServiceLocator.stores,
3337
storage: StorageManagerType = ServiceLocator.storageManager) {
@@ -92,6 +96,7 @@ private extension BookingDetailsViewModel {
9296
}
9397
headerContent.update(with: booking)
9498
appointmentDetailsContent.update(with: booking, resource: bookingResource)
99+
attendanceContent.update(with: booking)
95100
paymentContent.update(with: booking)
96101
}
97102

@@ -137,6 +142,24 @@ extension BookingDetailsViewModel {
137142
}
138143
}
139144

145+
// MARK: Attendance status
146+
147+
extension BookingDetailsViewModel {
148+
func updateAttendanceStatus(to newStatus: BookingAttendanceStatus) {
149+
let action = BookingAction.updateBookingAttendanceStatus(
150+
siteID: booking.siteID,
151+
bookingID: booking.bookingID,
152+
status: newStatus
153+
) { error in
154+
if let error {
155+
DDLogError("⛔️ Error updating booking attendance status: \(error)")
156+
// TODO: Show an error notice to the user
157+
}
158+
}
159+
stores.dispatch(action)
160+
}
161+
}
162+
140163
private extension BookingDetailsViewModel {
141164
@MainActor
142165
func fetchResource() async -> BookingResource? {

0 commit comments

Comments
 (0)