Skip to content

Commit 9129e60

Browse files
authored
Bookings: Enable searching in service/event filter (#16294)
2 parents 605fc9f + ebae5bf commit 9129e60

File tree

8 files changed

+551
-6
lines changed

8 files changed

+551
-6
lines changed

WooCommerce/Classes/Bookings/BookingFilters/BookableProductListSyncable.swift

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ struct BookableProductListSyncable: ListSyncable {
1111

1212
let title = Localization.title
1313

14-
let emptyStateMessage = Localization.noMembersFound
14+
let emptyStateMessage = Localization.noServiceFound
1515
let emptyItemTitlePlaceholder: String? = nil
1616

17-
let searchConfiguration: ListSearchConfiguration? = nil
17+
let searchConfiguration: ListSearchConfiguration? = ListSearchConfiguration(
18+
searchPrompt: Localization.searchPrompt,
19+
emptySearchTitle: Localization.noServiceFound,
20+
emptySearchDescription: Localization.emptySearchDescription
21+
)
1822

1923
let selectionDisabledMessage: String? = nil
2024

@@ -53,7 +57,19 @@ struct BookableProductListSyncable: ListSyncable {
5357

5458
/// Creates the action to search items with keyword
5559
func createSearchAction(keyword: String, pageNumber: Int, pageSize: Int, completion: @escaping (Result<Bool, Error>) -> Void) -> Action {
56-
fatalError("Searching is not supported")
60+
ProductAction.searchProducts(
61+
siteID: siteID,
62+
keyword: keyword,
63+
pageNumber: pageNumber,
64+
pageSize: pageSize,
65+
productType: .booking,
66+
onCompletion: completion
67+
)
68+
}
69+
70+
/// Creates the predicate for filtering search results
71+
func createSearchPredicate(keyword: String) -> NSPredicate? {
72+
NSPredicate(format: "SUBQUERY(searchResults, $result, $result.keyword = %@).@count > 0", keyword)
5773
}
5874

5975
// MARK: - Display Configuration
@@ -79,10 +95,20 @@ private extension BookableProductListSyncable {
7995
value: "Service / Event",
8096
comment: "Title of the booking service/event selector view"
8197
)
82-
static let noMembersFound = NSLocalizedString(
98+
static let noServiceFound = NSLocalizedString(
8399
"bookingServiceEventSelectorView.noMembersFound",
84100
value: "No service or event found",
85101
comment: "Text on the empty view of the booking service/event selector view"
86102
)
103+
static let searchPrompt = NSLocalizedString(
104+
"bookingServiceEventSelectorView.searchPrompt",
105+
value: "Search service / event",
106+
comment: "Prompt in the search bar of the booking service/event selector view"
107+
)
108+
static let emptySearchDescription = NSLocalizedString(
109+
"bookingServiceEventSelectorView.emptySearchDescription",
110+
value: "Try adjusting your search term to see more results",
111+
comment: "Message on the empty search result view of the booking service/event selector view"
112+
)
87113
}
88114
}

WooCommerce/Classes/Bookings/BookingFilters/CustomerListSyncable.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ struct CustomerListSyncable: ListSyncable {
6666
)
6767
}
6868

69+
/// Creates the predicate for filtering search results
70+
/// - Returns: nil because customer search handles filtering directly
71+
func createSearchPredicate(keyword: String) -> NSPredicate? {
72+
nil
73+
}
74+
6975
// MARK: - Display Configuration
7076

7177
func displayName(for item: Customer) -> String {

WooCommerce/Classes/Bookings/BookingFilters/ListSyncable.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ protocol ListSyncable {
3030
/// Creates the action to search items with keyword
3131
func createSearchAction(keyword: String, pageNumber: Int, pageSize: Int, completion: @escaping (Result<Bool, Error>) -> Void) -> Action
3232

33+
/// Creates the predicate for filtering search results
34+
/// - Parameter keyword: The search keyword
35+
/// - Returns: A predicate to filter storage objects by search results, or nil if search predicate is not needed
36+
func createSearchPredicate(keyword: String) -> NSPredicate?
37+
3338
// MARK: - Display Configuration
3439

3540
/// Returns the display name for an item

WooCommerce/Classes/Bookings/BookingFilters/SyncableListSelectorView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,16 @@ private extension SyncableListSelectorView {
7777
}
7878
)
7979
.renderedIf(viewModel.searchQuery.isEmpty)
80+
.listRowSeparator(.hidden, edges: .top)
8081

81-
ForEach(items, id: \.self) { item in
82+
ForEach(Array(items.enumerated()), id: \.element) { (index, item) in
8283
optionRow(text: syncable.displayName(for: item),
8384
description: syncable.description(for: item),
8485
isSelected: selectedItems.contains(where: { $0 == syncable.filterItem(for: item) }),
8586
onSelection: { toggleSelectionIfPossible(for: item) })
87+
.if(index == 0 && viewModel.searchQuery.isNotEmpty) {
88+
$0.listRowSeparator(.hidden, edges: .top)
89+
}
8690
}
8791

8892
InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator)

WooCommerce/Classes/Bookings/BookingFilters/SyncableListSelectorViewModel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,15 @@ final class SyncableListSelectorViewModel<Syncable: ListSyncable>: ObservableObj
8181

8282
/// Handles search query changes by resetting pagination and triggering new search
8383
private func handleSearchQueryChange(_ query: String) {
84-
syncState = .syncingFirstPage
8584
currentSearchKeyword = query
85+
86+
// Update the predicate to filter by search results if needed
87+
var predicates = [syncable.createPredicate()]
88+
if !query.isEmpty, let searchPredicate = syncable.createSearchPredicate(keyword: query) {
89+
predicates.append(searchPredicate)
90+
}
91+
resultsController.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
92+
8693
paginationTracker.syncFirstPage()
8794
}
8895

WooCommerce/Classes/Bookings/BookingFilters/TeamMemberListSyncable.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ struct TeamMemberListSyncable: ListSyncable {
4848
fatalError("Searching is not supported")
4949
}
5050

51+
/// Creates the predicate for filtering search results
52+
/// - Returns: nil because searching is not supported for team members
53+
func createSearchPredicate(keyword: String) -> NSPredicate? {
54+
nil
55+
}
56+
5157
// MARK: - Display Configuration
5258

5359
func displayName(for item: BookingResource) -> String {

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,7 @@
24672467
DE8AA0B32BBE55E40084D2CC /* DashboardViewHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8AA0B22BBE55E40084D2CC /* DashboardViewHostingController.swift */; };
24682468
DE8AA0B52BBEBE590084D2CC /* ViewControllerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8AA0B42BBEBE590084D2CC /* ViewControllerContainer.swift */; };
24692469
DE8C22732EB0AE8500C69F35 /* MultiSelectListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8C22722EB0AE8500C69F35 /* MultiSelectListView.swift */; };
2470+
DE8C3A002EB3527300C69F35 /* SyncableListSelectorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8C39FF2EB3527300C69F35 /* SyncableListSelectorViewModelTests.swift */; };
24702471
DE8C63AE2E1E2D2D00DA48AC /* OrderDetailsShipmentDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8C63AD2E1E2D1400DA48AC /* OrderDetailsShipmentDetailsView.swift */; };
24712472
DE8C946E264699B600C94823 /* PluginListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8C946D264699B600C94823 /* PluginListViewModel.swift */; };
24722473
DE96844B2A331AD2000FBF4E /* WooAnalyticsEvent+ProductSharingAI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE96844A2A331AD2000FBF4E /* WooAnalyticsEvent+ProductSharingAI.swift */; };
@@ -5395,6 +5396,7 @@
53955396
DE8AA0B22BBE55E40084D2CC /* DashboardViewHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardViewHostingController.swift; sourceTree = "<group>"; };
53965397
DE8AA0B42BBEBE590084D2CC /* ViewControllerContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContainer.swift; sourceTree = "<group>"; };
53975398
DE8C22722EB0AE8500C69F35 /* MultiSelectListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectListView.swift; sourceTree = "<group>"; };
5399+
DE8C39FF2EB3527300C69F35 /* SyncableListSelectorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncableListSelectorViewModelTests.swift; sourceTree = "<group>"; };
53985400
DE8C63AD2E1E2D1400DA48AC /* OrderDetailsShipmentDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsShipmentDetailsView.swift; sourceTree = "<group>"; };
53995401
DE8C946D264699B600C94823 /* PluginListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginListViewModel.swift; sourceTree = "<group>"; };
54005402
DE96844A2A331AD2000FBF4E /* WooAnalyticsEvent+ProductSharingAI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+ProductSharingAI.swift"; sourceTree = "<group>"; };
@@ -12502,6 +12504,7 @@
1250212504
DED1E3162E8556270089909C /* Bookings */ = {
1250312505
isa = PBXGroup;
1250412506
children = (
12507+
DE8C39FF2EB3527300C69F35 /* SyncableListSelectorViewModelTests.swift */,
1250512508
DE49CD212E966814006DCB07 /* BookingSearchViewModelTests.swift */,
1250612509
DED1E3152E8556270089909C /* BookingListViewModelTests.swift */,
1250712510
2D054A292E953E3C004111FD /* BookingDetailsViewModelTests.swift */,
@@ -16006,6 +16009,7 @@
1600616009
746FC23D2200A62B00C3096C /* DateWooTests.swift in Sources */,
1600716010
DEF8CF1129A8933E00800A60 /* JetpackBenefitsViewModelTests.swift in Sources */,
1600816011
31F21B5A263CB41A0035B50A /* MockCardPresentPaymentsStoresManager.swift in Sources */,
16012+
DE8C3A002EB3527300C69F35 /* SyncableListSelectorViewModelTests.swift in Sources */,
1600916013
86F0896F2B307D7E00D668A1 /* ThemesPreviewViewModelTests.swift in Sources */,
1601016014
CC3B35DF28E5BE6F0036B097 /* ReviewReplyViewModelTests.swift in Sources */,
1601116015
DEE215322D116FBB004A11F3 /* EditStoreListViewModelTests.swift in Sources */,

0 commit comments

Comments
 (0)