Skip to content

Commit 182e524

Browse files
committed
Add selector view
1 parent 3ec173f commit 182e524

File tree

4 files changed

+165
-11
lines changed

4 files changed

+165
-11
lines changed

WooCommerce/Classes/Bookings/BookingFilters/BookingFiltersViewModel.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,9 @@ private extension BookingFiltersViewModel.BookingListFilter {
182182
extension BookingFiltersViewModel.BookingListFilter {
183183
func createViewModel(filters: BookingFiltersViewModel.Filters) -> FilterTypeViewModel {
184184
switch self {
185-
case .teamMember:
186-
// TODO: Implement team member selector when available
187-
// For now, using static options with nil (Any option)
188-
let options: [BookingResource?] = [nil]
185+
case .teamMember(let siteID):
189186
return FilterTypeViewModel(title: title,
190-
listSelectorConfig: .staticOptions(options: options),
187+
listSelectorConfig: .bookingResource(siteID: siteID),
191188
selectedValue: filters.teamMember)
192189
case .product(let siteID):
193190
return FilterTypeViewModel(title: title,
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import SwiftUI
2+
import Yosemite
3+
4+
struct BookingTeamMemberSelectorView: View {
5+
@ObservedObject private var viewModel: BookingTeamMemberSelectorViewModel
6+
@State var selectedMember: BookingResource?
7+
8+
private let onSelection: (BookingResource?) -> Void
9+
10+
init(viewModel: BookingTeamMemberSelectorViewModel,
11+
selectedMember: BookingResource?,
12+
onSelection: @escaping (BookingResource?) -> Void) {
13+
self.viewModel = viewModel
14+
self.selectedMember = selectedMember
15+
self.onSelection = onSelection
16+
}
17+
18+
var body: some View {
19+
VStack {
20+
switch viewModel.syncState {
21+
case .empty:
22+
emptyStateView
23+
case .syncingFirstPage:
24+
loadingView
25+
case .results:
26+
resourceList(with: viewModel.resources,
27+
onNextPage: { viewModel.onLoadNextPageAction() })
28+
}
29+
}
30+
.task {
31+
viewModel.loadResources()
32+
}
33+
.navigationTitle(Localization.title)
34+
.navigationBarTitleDisplayMode(.inline)
35+
.onChange(of: selectedMember) { _, newValue in
36+
onSelection(newValue)
37+
}
38+
}
39+
}
40+
41+
private extension BookingTeamMemberSelectorView {
42+
var loadingView: some View {
43+
VStack {
44+
Spacer()
45+
ProgressView().progressViewStyle(.circular)
46+
Spacer()
47+
}
48+
.frame(maxWidth: .infinity)
49+
.background(Color(.systemBackground))
50+
}
51+
52+
func resourceList(with members: [BookingResource],
53+
onNextPage: @escaping () -> Void) -> some View {
54+
List {
55+
optionRow(text: Localization.any,
56+
isSelected: selectedMember == nil,
57+
onSelection: { selectedMember = nil })
58+
59+
ForEach(members, id: \.resourceID) { member in
60+
optionRow(text: member.name,
61+
isSelected: member == selectedMember,
62+
onSelection: { selectedMember = member})
63+
}
64+
65+
InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator)
66+
.padding(.top, Layout.viewPadding)
67+
.onAppear {
68+
onNextPage()
69+
}
70+
}
71+
.listStyle(.plain)
72+
.background(Color(.listBackground))
73+
}
74+
75+
func optionRow(text: String, isSelected: Bool, onSelection: @escaping () -> Void) -> some View {
76+
HStack {
77+
Text(text)
78+
Spacer()
79+
Image(systemName: "checkmark")
80+
.font(.body.weight(.medium))
81+
.foregroundStyle(Color.accentColor)
82+
.renderedIf(isSelected)
83+
}
84+
.tappable {
85+
onSelection()
86+
}
87+
.listRowBackground(Color(.listForeground(modal: false)))
88+
}
89+
90+
var emptyStateView: some View {
91+
VStack {
92+
Spacer()
93+
Text(Localization.noMembersFound)
94+
.secondaryBodyStyle()
95+
Spacer()
96+
}
97+
.multilineTextAlignment(.center)
98+
.padding(.horizontal, Layout.viewPadding)
99+
.background(Color(.systemBackground))
100+
}
101+
}
102+
103+
private extension BookingTeamMemberSelectorView {
104+
enum Layout {
105+
static let textVerticalPadding: CGFloat = 8
106+
static let viewPadding: CGFloat = 16
107+
static let cornerRadius: CGFloat = 8
108+
}
109+
110+
enum Localization {
111+
static let title = NSLocalizedString(
112+
"bookingTeamMemberSelectorView.title",
113+
value: "Team member",
114+
comment: "Title of the booking team member selector view"
115+
)
116+
static let any = NSLocalizedString(
117+
"bookingTeamMemberSelectorView.any",
118+
value: "Any",
119+
comment: "Option to select no filter on the booking team member selector view"
120+
)
121+
static let noMembersFound = NSLocalizedString(
122+
"bookingTeamMemberSelectorView.noMembersFound",
123+
value: "No team members found",
124+
comment: "Text on the empty view of the booking team member selector view"
125+
)
126+
}
127+
}
128+
129+
// MARK: - Hosting Controller
130+
final class BookingTeamMemberSelectorHostingController: UIHostingController<BookingTeamMemberSelectorView> {
131+
init(viewModel: BookingTeamMemberSelectorViewModel,
132+
selectedMember: BookingResource?,
133+
onSelection: @escaping (BookingResource?) -> Void) {
134+
super.init(rootView: BookingTeamMemberSelectorView(
135+
viewModel: viewModel,
136+
selectedMember: selectedMember,
137+
onSelection: onSelection
138+
))
139+
}
140+
141+
@available(*, unavailable)
142+
required init?(coder aDecoder: NSCoder) {
143+
fatalError("init(coder:) has not been implemented")
144+
}
145+
}

WooCommerce/Classes/Bookings/BookingFilters/BookingResourceListViewModel.swift renamed to WooCommerce/Classes/Bookings/BookingFilters/BookingTeamMemberSelectorViewModel.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import Foundation
22
import Yosemite
33
import protocol Storage.StorageManagerType
44

5-
// View model for `BookingResourceListView`
6-
final class BookingResourceListViewModel: ObservableObject {
5+
// View model for `BookingTeamMemberSelectorView`
6+
final class BookingTeamMemberSelectorViewModel: ObservableObject {
77
@Published private(set) var resources: [BookingResource] = []
88

99
/// Keeps track of the current state of the syncing
@@ -37,7 +37,7 @@ final class BookingResourceListViewModel: ObservableObject {
3737
self.stores = stores
3838
self.storage = storage
3939
self.paginationTracker = PaginationTracker(pageFirstIndex: pageFirstIndex)
40-
40+
4141
configureResultsController()
4242
configurePaginationTracker()
4343
}
@@ -53,7 +53,7 @@ final class BookingResourceListViewModel: ObservableObject {
5353
}
5454
}
5555

56-
private extension BookingResourceListViewModel {
56+
private extension BookingTeamMemberSelectorViewModel {
5757
func configurePaginationTracker() {
5858
paginationTracker.delegate = self
5959
}
@@ -81,7 +81,7 @@ private extension BookingResourceListViewModel {
8181
}
8282
}
8383

84-
extension BookingResourceListViewModel: PaginationTrackerDelegate {
84+
extension BookingTeamMemberSelectorViewModel: PaginationTrackerDelegate {
8585
func sync(pageNumber: Int, pageSize: Int, reason: String?, onCompletion: SyncCompletion?) {
8686
transitionToSyncingState()
8787
let action = BookingAction.synchronizeResources(
@@ -106,7 +106,7 @@ extension BookingResourceListViewModel: PaginationTrackerDelegate {
106106

107107
// MARK: State Machine
108108

109-
extension BookingResourceListViewModel {
109+
extension BookingTeamMemberSelectorViewModel {
110110
/// Represents possible states for syncing bookings.
111111
enum SyncState: Equatable {
112112
case syncingFirstPage

WooCommerce/Classes/ViewRelated/Filters/FilterListViewController.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ enum FilterListValueSelectorConfig {
9393
case products(siteID: Int64)
9494
// Filter list selector for customer
9595
case customer(siteID: Int64)
96+
// Filter list selector for booking team member
97+
case bookingResource(siteID: Int64)
9698

9799
}
98100

@@ -358,6 +360,16 @@ private extension FilterListViewController {
358360
WooNavigationController(rootViewController: controller),
359361
animated: true
360362
)
363+
case .bookingResource(let siteID):
364+
let selectedMember = selected.selectedValue as? BookingResource
365+
let viewModel = BookingTeamMemberSelectorViewModel(siteID: siteID)
366+
let hostingController = BookingTeamMemberSelectorHostingController(viewModel: viewModel, selectedMember: selectedMember) { [weak self] resource in
367+
selected.selectedValue = resource
368+
self?.updateUI(numberOfActiveFilters: self?.viewModel.filterTypeViewModels.numberOfActiveFilters ?? 0)
369+
self?.listSelector.reloadData()
370+
self?.listSelector.navigationController?.popViewController(animated: true)
371+
}
372+
listSelector.navigationController?.pushViewController(hostingController, animated: true)
361373
}
362374
}
363375
}

0 commit comments

Comments
 (0)