diff --git a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift index 8d33f66e49d..8193cd3d8aa 100644 --- a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift +++ b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift @@ -4,6 +4,8 @@ import Networking struct BookingDetailsView: View { @Environment(\.safeAreaInsets) var safeAreaInsets: EdgeInsets @Environment(\.dismiss) private var dismiss + @State private var showingOptions = false + @State private var showingStatusSheet = false @ObservedObject private var viewModel: BookingDetailsViewModel @@ -51,13 +53,31 @@ struct BookingDetailsView: View { } ToolbarItem(placement: .navigationBarTrailing) { Button { - //TODO: - present an action sheet - print("On ellipsis item tap") + showingOptions = true } label: { Image(systemName: "ellipsis") } + .confirmationDialog("", isPresented: $showingOptions, titleVisibility: .hidden) { + Button(Localization.markAsPaid) { + print("On mark as paid tap") + } + Button(Localization.viewOrder) { + print("On view order tap") + } + Button(Localization.cancelBookingAction, role: .destructive) { + print("On cancel booking tap") + } + } } } + .sheet(isPresented: $showingStatusSheet) { + UpdateAttendanceStatusView { selectedStatus in + print("Selected status: \(selectedStatus)") + } + .padding(.top) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + } } } @@ -145,7 +165,9 @@ private extension BookingDetailsView { value: .placeholder(content.value), selectionStyle: .disclosure, horizontalPadding: 0 - ) + ) { + showingStatusSheet = true + } } func appointmentDetailsView(with content: BookingDetailsViewModel.AppointmentDetailsContent) -> some View { @@ -312,6 +334,22 @@ private extension View { private extension BookingDetailsView { enum Localization { + static let markAsPaid = NSLocalizedString( + "BookingDetailsView.options.markAsPaid", + value: "Mark as paid", + comment: "Action sheet option to mark a booking as paid." + ) + static let viewOrder = NSLocalizedString( + "BookingDetailsView.options.viewOrder", + value: "View order", + comment: "Action sheet option to view the order for a booking." + ) + static let cancelBookingAction = NSLocalizedString( + "BookingDetailsView.options.cancelBooking", + value: "Cancel booking", + comment: "Action sheet option to cancel a booking." + ) + static let cancelBooking = NSLocalizedString( "BookingDetailsView.customer.cancelBookingButton.title", value: "Cancel booking", diff --git a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/UpdateAttendanceStatusView.swift b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/UpdateAttendanceStatusView.swift new file mode 100644 index 00000000000..cb91d8b7023 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/UpdateAttendanceStatusView.swift @@ -0,0 +1,144 @@ +import SwiftUI + +struct UpdateAttendanceStatusView: View { + @Environment(\.dismiss) private var dismiss + private let statuses = AttendanceStatus.allCases + private let onStatusSelected: (AttendanceStatus) -> Void + + init(onStatusSelected: @escaping (AttendanceStatus) -> Void) { + self.onStatusSelected = onStatusSelected + } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 24) { + Text(Localization.title) + .font(.subheadline.weight(.medium)) + .foregroundColor(.secondary) + .padding(.horizontal) + + ForEach(statuses) { status in + HStack(alignment: .top, spacing: 16) { + Image(systemName: status.iconName) + .font(.title3.weight(.medium)) + .foregroundStyle(Color(.systemGray)) + VStack(alignment: .leading, spacing: 4) { + Text(status.title) + .font(.body.weight(.medium)) + .fixedSize(horizontal: false, vertical: true) + Text(status.description) + .font(.subheadline) + .foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal) + .contentShape(Rectangle()) + .tappable { + onStatusSelected(status) + dismiss() + } + } + } + .padding(.top) + } + } +} + +extension UpdateAttendanceStatusView { + enum AttendanceStatus: CaseIterable, Identifiable { + case booked + case checkedIn + case noShow + + var id: Self { self } + } +} + +private extension UpdateAttendanceStatusView.AttendanceStatus { + var title: String { + switch self { + case .booked: + return UpdateAttendanceStatusView.Localization.bookedTitle + case .checkedIn: + return UpdateAttendanceStatusView.Localization.checkedInTitle + case .noShow: + return UpdateAttendanceStatusView.Localization.noShowTitle + } + } + + var description: String { + switch self { + case .booked: + return UpdateAttendanceStatusView.Localization.bookedDescription + case .checkedIn: + return UpdateAttendanceStatusView.Localization.checkedInDescription + case .noShow: + return UpdateAttendanceStatusView.Localization.noShowDescription + } + } + + var iconName: String { + switch self { + case .booked: + return "calendar.badge.checkmark" + case .checkedIn: + return "calendar.and.person" + case .noShow: + return "calendar.badge.exclamationmark" + } + } +} + +private extension UpdateAttendanceStatusView { + enum Localization { + static let title = NSLocalizedString( + "UpdateAttendanceStatusView.title", + value: "Update attendance status", + comment: "Title of the update attendance status bottom sheet." + ) + + static let bookedTitle = NSLocalizedString( + "UpdateAttendanceStatusView.booked.title", + value: "Booked", + comment: "Title for the 'Booked' attendance status." + ) + static let bookedDescription = NSLocalizedString( + "UpdateAttendanceStatusView.booked.description", + value: "The appointment is scheduled but hasn't happened yet.", + comment: "Description for the 'Booked' attendance status." + ) + + static let checkedInTitle = NSLocalizedString( + "UpdateAttendanceStatusView.checkedIn.title", + value: "Checked-in", + comment: "Title for the 'Checked-in' attendance status." + ) + static let checkedInDescription = NSLocalizedString( + "UpdateAttendanceStatusView.checkedIn.description", + value: "The customer arrived and the session took place as planned.", + comment: "Description for the 'Checked-in' attendance status." + ) + + static let noShowTitle = NSLocalizedString( + "UpdateAttendanceStatusView.noShow.title", + value: "No-show", + comment: "Title for the 'No-show' attendance status." + ) + static let noShowDescription = NSLocalizedString( + "UpdateAttendanceStatusView.noShow.description", + value: "The client missed the appointment without canceling in advance.", + comment: "Description for the 'No-show' attendance status." + ) + } +} + +#if DEBUG +struct UpdateAttendanceStatusView_Previews: PreviewProvider { + static var previews: some View { + UpdateAttendanceStatusView { selectedStatus in + print("Selected status: \(selectedStatus)") + } + } +} +#endif diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 39d7eae2202..9042a417463 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1261,6 +1261,7 @@ 2D05E8112E8A9905004111FD /* CustomerContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05E8102E8A98FE004111FD /* CustomerContent.swift */; }; 2D05E8132E8AADB9004111FD /* PaymentContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05E8122E8AADB2004111FD /* PaymentContent.swift */; }; 2D05F7102E8BE921004111FD /* View+Tappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05F70F2E8BE91E004111FD /* View+Tappable.swift */; }; + 2D05FE962E8D71EA004111FD /* UpdateAttendanceStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FE952E8D71EA004111FD /* UpdateAttendanceStatusView.swift */; }; 2D09E0D12E61BC7F005C26F3 /* ApplicationPasswordsExperimentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D09E0D02E61BC7D005C26F3 /* ApplicationPasswordsExperimentState.swift */; }; 2D09E0D52E65C9B9005C26F3 /* ApplicationPasswordsExperimentStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D09E0D42E65C9B9005C26F3 /* ApplicationPasswordsExperimentStateTests.swift */; }; 2D7A3E232E7891DB00C46401 /* CIABEligibilityCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7A3E222E7891D200C46401 /* CIABEligibilityCheckerTests.swift */; }; @@ -4469,6 +4470,7 @@ 2D05E8102E8A98FE004111FD /* CustomerContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerContent.swift; sourceTree = ""; }; 2D05E8122E8AADB2004111FD /* PaymentContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentContent.swift; sourceTree = ""; }; 2D05F70F2E8BE91E004111FD /* View+Tappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Tappable.swift"; sourceTree = ""; }; + 2D05FE952E8D71EA004111FD /* UpdateAttendanceStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateAttendanceStatusView.swift; sourceTree = ""; }; 2D09E0D02E61BC7D005C26F3 /* ApplicationPasswordsExperimentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordsExperimentState.swift; sourceTree = ""; }; 2D09E0D42E65C9B9005C26F3 /* ApplicationPasswordsExperimentStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordsExperimentStateTests.swift; sourceTree = ""; }; 2D7A3E222E7891D200C46401 /* CIABEligibilityCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIABEligibilityCheckerTests.swift; sourceTree = ""; }; @@ -9184,6 +9186,7 @@ isa = PBXGroup; children = ( 2DAC2C972E82A185008521AF /* BookingDetailsView.swift */, + 2D05FE952E8D71EA004111FD /* UpdateAttendanceStatusView.swift */, ); path = "Booking Details"; sourceTree = ""; @@ -16558,6 +16561,7 @@ DE2FE5862925DA050018040A /* SiteCredentialLoginView.swift in Sources */, 020DD48F232392C9005822B1 /* UIViewController+AppReview.swift in Sources */, 2687165524D21BC80042F6AE /* SurveySubmittedViewController.swift in Sources */, + 2D05FE962E8D71EA004111FD /* UpdateAttendanceStatusView.swift in Sources */, CE263DE8206ACE3E0015A693 /* MainTabBarController.swift in Sources */, 20A3AFE32B10EF860033AF2D /* CardReaderSettingsFlowPresentingView.swift in Sources */, CE14452E2188C11700A991D8 /* ZendeskManager.swift in Sources */,