Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Yosemite
struct BookableProductListSyncable: ListSyncable {
typealias StorageType = StorageProduct
typealias ModelType = Product
typealias ListFilterType = BookingProductFilter

let siteID: Int64

Expand Down Expand Up @@ -50,6 +51,10 @@ struct BookableProductListSyncable: ListSyncable {
func displayName(for item: Product) -> String {
item.name
}

func filterItem(for item: Product) -> BookingProductFilter {
BookingProductFilter(productID: item.productID, name: item.name)
}
}

private extension BookableProductListSyncable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ final class BookingFiltersViewModel: FilterListViewModel {
}

var criteria: Filters {
let teamMember = teamMemberFilterViewModel.selectedValue as? BookingResource
let product = productFilterViewModel.selectedValue as? BookingProductFilter
let teamMembers = (teamMemberFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingResource] ?? []
let products = (productFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingProductFilter] ?? []
let customer = customerFilterViewModel.selectedValue as? CustomerFilter
let attendanceStatus = attendanceStatusFilterViewModel.selectedValue as? BookingAttendanceStatus
let paymentStatus = paymentStatusFilterViewModel.selectedValue as? BookingStatus
let attendanceStatuses = (attendanceStatusFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingAttendanceStatus] ?? []
let paymentStatuses = (paymentStatusFilterViewModel.selectedValue as? MultipleFilterSelection)?.items as? [BookingStatus] ?? []
let dateRange = dateTimeFilterViewModel.selectedValue as? BookingDateRangeFilter
let numberOfActiveFilters = filterTypeViewModels.numberOfActiveFilters

return Filters(teamMember: teamMember,
product: product,
attendanceStatus: attendanceStatus,
paymentStatus: paymentStatus,
return Filters(teamMembers: teamMembers,
products: products,
attendanceStatuses: attendanceStatuses,
paymentStatuses: paymentStatuses,
customer: customer,
dateRange: dateRange,
numberOfActiveFilters: numberOfActiveFilters)
Expand Down Expand Up @@ -86,55 +86,47 @@ final class BookingFiltersViewModel: FilterListViewModel {

struct Filters: Equatable, HumanReadable {

let teamMember: BookingResource?
let product: BookingProductFilter?
let attendanceStatus: BookingAttendanceStatus?
let paymentStatus: BookingStatus?
let teamMembers: [BookingResource]
let products: [BookingProductFilter]
let attendanceStatuses: [BookingAttendanceStatus]
let paymentStatuses: [BookingStatus]
let customer: CustomerFilter?
let dateRange: BookingDateRangeFilter?

let numberOfActiveFilters: Int

init() {
teamMember = nil
product = nil
attendanceStatus = nil
paymentStatus = nil
teamMembers = []
products = []
attendanceStatuses = []
paymentStatuses = []
customer = nil
dateRange = nil
numberOfActiveFilters = 0
}

init(teamMember: BookingResource?,
product: BookingProductFilter?,
attendanceStatus: BookingAttendanceStatus?,
paymentStatus: BookingStatus?,
init(teamMembers: [BookingResource],
products: [BookingProductFilter],
attendanceStatuses: [BookingAttendanceStatus],
paymentStatuses: [BookingStatus],
customer: CustomerFilter?,
dateRange: BookingDateRangeFilter?,
numberOfActiveFilters: Int) {
self.teamMember = teamMember
self.product = product
self.attendanceStatus = attendanceStatus
self.paymentStatus = paymentStatus
self.teamMembers = teamMembers
self.products = products
self.attendanceStatuses = attendanceStatuses
self.paymentStatuses = paymentStatuses
self.customer = customer
self.dateRange = dateRange
self.numberOfActiveFilters = numberOfActiveFilters
}

var readableString: String {
var readable: [String] = []
if let teamMember {
readable.append(teamMember.name)
}
if let product {
readable.append(product.name)
}
if let attendanceStatus {
readable.append(attendanceStatus.localizedTitle)
}
if let paymentStatus {
readable.append(paymentStatus.localizedTitle)
}
var readable: [String] = teamMembers.map { $0.name } +
products.map { $0.name } +
attendanceStatuses.map { $0.localizedTitle } +
paymentStatuses.map { $0.localizedTitle }

if let customer {
readable.append(customer.description)
}
Expand Down Expand Up @@ -185,25 +177,25 @@ extension BookingFiltersViewModel.BookingListFilter {
case .teamMember(let siteID):
return FilterTypeViewModel(title: title,
listSelectorConfig: .bookingResource(siteID: siteID),
selectedValue: filters.teamMember)
selectedValue: MultipleFilterSelection(items: filters.teamMembers))
case .product(let siteID):
return FilterTypeViewModel(title: title,
listSelectorConfig: .bookableProduct(siteID: siteID),
selectedValue: filters.product)
selectedValue: MultipleFilterSelection(items: filters.products))
case .customer(let siteID):
return FilterTypeViewModel(title: title,
listSelectorConfig: .customer(siteID: siteID, source: .booking),
selectedValue: filters.customer)
case .attendanceStatus:
let options: [BookingAttendanceStatus?] = [nil, .booked, .checkedIn, .cancelled, .noShow]
let options: [BookingAttendanceStatus?] = [.booked, .checkedIn, .cancelled, .noShow]
return FilterTypeViewModel(title: title,
listSelectorConfig: .staticOptions(options: options),
selectedValue: filters.attendanceStatus)
listSelectorConfig: .multiSelectStaticOptions(options: options),
selectedValue: MultipleFilterSelection(items: filters.attendanceStatuses))
case .paymentStatus:
let options: [BookingStatus?] = [nil, .complete, .paid, .unpaid, .cancelled, .pendingConfirmation, .confirmed]
let options: [BookingStatus?] = [.complete, .paid, .unpaid, .cancelled, .pendingConfirmation, .confirmed]
return FilterTypeViewModel(title: title,
listSelectorConfig: .staticOptions(options: options),
selectedValue: filters.paymentStatus)
listSelectorConfig: .multiSelectStaticOptions(options: options),
selectedValue: MultipleFilterSelection(items: filters.paymentStatuses))
case .dateTime:
return FilterTypeViewModel(title: title,
listSelectorConfig: .bookingDateTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Yosemite
protocol ListSyncable {
associatedtype StorageType: ResultsControllerMutableType
associatedtype ModelType: Equatable & Hashable where ModelType == StorageType.ReadOnlyType
associatedtype ListFilterType: FilterType & Equatable

var title: String { get }
var emptyStateMessage: String { get }
Expand All @@ -27,4 +28,7 @@ protocol ListSyncable {

/// Returns the display name for an item
func displayName(for item: ModelType) -> String

/// Returns the filter type for an item
func filterItem(for item: ModelType) -> ListFilterType
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import SwiftUI

struct SyncableListSelectorView<Syncable: ListSyncable>: View {
@ObservedObject private var viewModel: SyncableListSelectorViewModel<Syncable>
@State var selectedItem: Syncable.ModelType?
@State private var selectedItems: [Syncable.ListFilterType]

private let syncable: Syncable
private let initialSelection: (Syncable.ModelType?) -> Bool
private let onSelection: (Syncable.ModelType?) -> Void
private let onSelection: ([Syncable.ListFilterType]) -> Void

private let viewPadding: CGFloat = 16

init(viewModel: SyncableListSelectorViewModel<Syncable>,
syncable: Syncable,
initialSelection: @escaping (Syncable.ModelType?) -> Bool,
onSelection: @escaping (Syncable.ModelType?) -> Void) {
initialSelections: [Syncable.ListFilterType],
onSelection: @escaping ([Syncable.ListFilterType]) -> Void) {
self.viewModel = viewModel
self.syncable = syncable
self.initialSelection = initialSelection
self.selectedItems = initialSelections
self.onSelection = onSelection
}

Expand All @@ -37,9 +36,6 @@ struct SyncableListSelectorView<Syncable: ListSyncable>: View {
}
.navigationTitle(syncable.title)
.navigationBarTitleDisplayMode(.inline)
.onChange(of: selectedItem) { _, newValue in
onSelection(newValue)
}
}
}

Expand All @@ -63,14 +59,17 @@ private extension SyncableListSelectorView {
value: "Any",
comment: "Option to select no filter on a list selector view"
),
isSelected: selectedItem == nil,
onSelection: { selectedItem = nil }
isSelected: selectedItems.isEmpty,
onSelection: {
selectedItems.removeAll()
onSelection([])
}
)

ForEach(items, id: \.self) { item in
optionRow(text: syncable.displayName(for: item),
isSelected: isItemSelected(item),
onSelection: { selectedItem = item })
isSelected: selectedItems.contains(where: { $0 == syncable.filterItem(for: item) }),
onSelection: { toggleSelection(for: item) })
}

InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator)
Expand All @@ -83,11 +82,14 @@ private extension SyncableListSelectorView {
.background(Color(.listBackground))
}

func isItemSelected(_ item: Syncable.ModelType?) -> Bool {
if let selectedItem {
return item == selectedItem
func toggleSelection(for item: Syncable.ModelType) {
let filterItem = syncable.filterItem(for: item)
if let index = selectedItems.firstIndex(of: filterItem) {
selectedItems.remove(at: index)
} else {
selectedItems.append(filterItem)
}
return initialSelection(item)
onSelection(selectedItems)
}

func optionRow(text: String, isSelected: Bool, onSelection: @escaping () -> Void) -> some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Yosemite
struct TeamMemberListSyncable: ListSyncable {
typealias StorageType = StorageBookingResource
typealias ModelType = BookingResource
typealias ListFilterType = BookingResource

let siteID: Int64

Expand Down Expand Up @@ -42,6 +43,10 @@ struct TeamMemberListSyncable: ListSyncable {
func displayName(for item: BookingResource) -> String {
item.name
}

func filterItem(for item: BookingResource) -> BookingResource {
item
}
}

private extension TeamMemberListSyncable {
Expand Down
Loading