From 3e7c442aa7f70f03e2b99c871d163f0be42d010c Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 3 Jun 2024 15:01:39 +0200 Subject: [PATCH] fix club stuff, fix cashier stuff --- PadelClub/Data/Club.swift | 2 +- PadelClub/Data/User.swift | 4 + PadelClub/Views/Cashier/CashierView.swift | 248 ++++++++++-------- PadelClub/Views/Club/ClubDetailView.swift | 113 ++++---- PadelClub/Views/Club/ClubsView.swift | 71 +++-- PadelClub/Views/Club/CreateClubView.swift | 31 ++- .../Club/Shared/ClubCourtSetupView.swift | 5 +- PadelClub/Views/Navigation/MainView.swift | 8 +- .../Views/Navigation/Umpire/UmpireView.swift | 2 +- .../Screen/TournamentCashierView.swift | 69 +++-- 10 files changed, 321 insertions(+), 232 deletions(-) diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index 14669ea..ccd2921 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -215,7 +215,7 @@ extension Club { identify a club : code, name, ?? */ - let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || $0.code == code }) + let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) if clubs.isEmpty == false { return clubs.first! diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index dde81c2..efd04c8 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -89,6 +89,10 @@ class User: ModelObject, UserBase, Storable { return Store.main.filter(isIncluded: { (includeCreated && $0.creator == id) || clubs.contains($0.id) }) } + func createdClubsObjectsNotFavorite() -> [Club] { + return Store.main.filter(isIncluded: { ($0.creator == id) || clubs.contains($0.id) == false }) + } + func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { if estimatedDuration == matchFormat.defaultEstimatedDuration { matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index dc708e9..af0414b 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -11,19 +11,18 @@ import Combine struct ShareableObject { let cashierViewModel: CashierViewModel - let teams: [TeamRegistration] + let players: [PlayerRegistration] let fileName: String func sharedData() async -> Data? { - let players = teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) - .flatMap({ $0.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) }) + let _players = players.filter({ cashierViewModel._shouldDisplayPlayer($0) }) .map { [$0.pasteData()] .compacted() .joined(separator: "\n") } .joined(separator: "\n\n") - return players.data(using: .utf8) + return _players.data(using: .utf8) } } @@ -43,6 +42,16 @@ extension ShareableObject: Transferable { } } +extension Array { + func sorted(by keyPath: KeyPath, order: SortOrder) -> [Element] { + switch order { + case .ascending: + return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } + case .descending: + return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] } + } + } +} class CashierViewModel: ObservableObject { let id: UUID = UUID() @@ -60,12 +69,18 @@ class CashierViewModel: ObservableObject { func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { if searchText.isEmpty == false { - filterOption.shouldDisplayPlayer(player) && player.contains(searchText) + sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) && player.contains(searchText) } else { - filterOption.shouldDisplayPlayer(player) + sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) } } + func _sortPlayers(_ players: [PlayerRegistration]) -> [PlayerRegistration] { + players + .filter({ _shouldDisplayPlayer($0) }) + .sorted { sortOption.compare($0, $1, order: sortOrder) } + } + enum SortOption: Int, Identifiable, CaseIterable { case teamRank case alphabeticalLastName @@ -74,6 +89,50 @@ class CashierViewModel: ObservableObject { case age case callDate + var sortingKeyPath: AnyKeyPath { + switch self { + case .alphabeticalLastName: + return \PlayerRegistration.lastName + case .alphabeticalFirstName: + return \PlayerRegistration.firstName + case .playerRank, .teamRank, .callDate: + return \PlayerRegistration.computedRank + case .age: + return \PlayerRegistration.computedAge! + } + } + + func compare(_ lhs: PlayerRegistration, _ rhs: PlayerRegistration, order: SortOrder) -> Bool { + switch self { + case .alphabeticalLastName: + return compare(lhs[keyPath: \.lastName], rhs[keyPath: \.lastName], order: order) + case .alphabeticalFirstName: + return compare(lhs[keyPath: \.firstName], rhs[keyPath: \.firstName], order: order) + case .playerRank, .teamRank, .callDate: + return compare(lhs[keyPath: \.computedRank], rhs[keyPath: \.computedRank], order: order) + case .age: + return compare(lhs[keyPath: \.computedAge!], rhs[keyPath: \.computedAge!], order: order) + } + } + + private func compare(_ lhs: T, _ rhs: T, order: SortOrder) -> Bool { + switch order { + case .ascending: + return lhs < rhs + case .descending: + return lhs > rhs + } + } + + func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { + switch self { + case .age: + player.computedAge != nil + default: + true + } + } + var id: Int { self.rawValue } func localizedLabel() -> String { switch self { @@ -129,14 +188,16 @@ class CashierViewModel: ObservableObject { struct CashierView: View { @EnvironmentObject var dataStore: DataStore @EnvironmentObject var cashierViewModel: CashierViewModel - + var tournaments : [Tournament] - var teams: [TeamRegistration] + @State private var teams: [TeamRegistration] + @State private var players: [PlayerRegistration] @State private var shareableObject: ShareableObject? init(tournament: Tournament, teams: [TeamRegistration]) { self.tournaments = [tournament] - self.teams = teams + _teams = .init(wrappedValue: teams) + _players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() })) } var body: some View { @@ -170,22 +231,21 @@ struct CashierView: View { } } + let filteredPlayers = cashierViewModel._sortPlayers(players) + if filteredPlayers.isEmpty { + _contentUnavailableView() + } + switch cashierViewModel.sortOption { case .teamRank: - _byTeamRankView() - case .alphabeticalLastName: - _byPlayerLastName() - case .alphabeticalFirstName: - _byPlayerFirstName() - case .playerRank: - _byPlayerRank() - case .age: - _byPlayerAge() + TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1) + case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age: + PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1) case .callDate: - _byCallDateView() + let _teams = teams.filter({ $0.callDate != nil }) + TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1) } } - .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) .onAppear { cashierViewModel.searchText = "" // if tournaments.count == 1 { @@ -196,7 +256,10 @@ struct CashierView: View { if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil { cashierViewModel.sortOption = .teamRank } - self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, teams: teams, fileName: "Encaissement.txt") + self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, players: players, fileName: "Encaissement") + } + .onChange(of: cashierViewModel.sortOrder) { + teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed() } .headerProminence(.increased) .toolbar { @@ -210,120 +273,85 @@ struct CashierView: View { } } } + + struct PlayerCashierView: View { + @EnvironmentObject var cashierViewModel: CashierViewModel + let players: [PlayerRegistration] + let displayTournamentTitle: Bool - @ViewBuilder - private func _byPlayer(_ players: [PlayerRegistration]) -> some View { - let _players = cashierViewModel.sortOrder == .ascending ? players : players.reversed() - if _players.isEmpty { - _contentUnavailableView() - } else { - ForEach(_players) { player in + var body: some View { + ForEach(players) { player in Section { EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) } header: { - HStack { - if let teamCallDate = player.team()?.callDate { - Text(teamCallDate.localizedDate()) - } - Spacer() - Text(player.formattedRank()) + if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() { + Text(tournamentTitle) } } footer: { - if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() { - Text(tournamentTitle) + if let teamCallDate = player.team()?.callDate { + Text("équipe convoqué") + Text(teamCallDate.localizedDate()) } } } } } - @ViewBuilder - private func _byPlayerRank() -> some View { - let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.computedRank)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) - _byPlayer(players) - } - - @ViewBuilder - private func _byPlayerAge() -> some View { - let players = teams.flatMap({ $0.unsortedPlayers() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) - _byPlayer(players) - } - - @ViewBuilder - private func _byPlayerLastName() -> some View { - let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.lastName)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) - _byPlayer(players) - } - - @ViewBuilder - private func _byPlayerFirstName() -> some View { - let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.firstName)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) - _byPlayer(players) - } - - @ViewBuilder - private func _byTeamRankView() -> some View { - let _teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed() - let _filteredTeams = _teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) - if _filteredTeams.isEmpty { - _contentUnavailableView() - } else { - ForEach(_filteredTeams) { team in - Section { - ForEach(team.players()) { player in - if cashierViewModel._shouldDisplayPlayer(player) { + struct TeamRankView: View { + @EnvironmentObject var cashierViewModel: CashierViewModel + let teams: [TeamRegistration] + let displayTournamentTitle: Bool + + var body: some View { + ForEach(teams) { team in + let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) + if players.isEmpty == false { + Section { + ForEach(players) { player in EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) } - } - } header: { - HStack { - if let callDate = team.callDate { - Text(callDate.localizedDate()) + } header: { + if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { + Text(tournamentTitle) } - Spacer() - VStack(alignment: .trailing, spacing: 0) { - Text("Poids").font(.caption) - Text(team.weight.formatted()) + } footer: { + if let callDate = team.callDate { + Text("convoqué") + Text(callDate.localizedDate()) } } - } footer: { - if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { - Text(tournamentTitle) - } } } } } - - @ViewBuilder - private func _byCallDateView() -> some View { - let _teams = teams.filter({ $0.callDate != nil && cashierViewModel._shouldDisplayTeam($0) }) - - if _teams.isEmpty { - _contentUnavailableView() - } - - let groupedTeams = Dictionary(grouping: _teams) { team in - team.callDate - } - let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() + + struct TeamCallDateView: View { + @EnvironmentObject var cashierViewModel: CashierViewModel + let teams: [TeamRegistration] + let displayTournamentTitle: Bool - ForEach(keys, id: \.self) { key in - if let _teams = groupedTeams[key] { - ForEach(_teams) { team in - Section { - ForEach(team.players()) { player in - if cashierViewModel._shouldDisplayPlayer(player) { - EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + var body: some View { + let groupedTeams = Dictionary(grouping: teams) { team in + team.callDate + } + let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() + + ForEach(keys, id: \.self) { key in + if let _teams = groupedTeams[key] { + ForEach(_teams) { team in + let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) + if players.isEmpty == false { + Section { + ForEach(players) { player in + EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + } + } header: { + Text(key.localizedDate()) + } footer: { + if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { + Text(tournamentTitle) + } } } - } header: { - Text(key.localizedDate()) - } footer: { - if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { - Text(tournamentTitle) - } } } } diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index 538b021..257493c 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -31,14 +31,43 @@ struct ClubDetailView: View { var body: some View { Form { + if displayContext == .edition || displayContext == .lockedForEditing { + let isFavorite = club.isFavorite() + Section { + RowButtonView(isFavorite ? "Retirer des favoris" : "Mettre en favori", role: isFavorite ? .destructive : nil) { + if isFavorite { + dataStore.user.clubs.removeAll(where: { $0 == club.id }) + } else { + dataStore.user.clubs.append(club.id) + } + self.dataStore.saveUser() + } + } footer: { + if displayContext == .lockedForEditing { + Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) + } + } + } + Section { - TextField("Nom du club", text: $club.name, axis: .vertical) - .lineLimit(2) + TextField("Nom du club", text: $club.name) .autocorrectionDisabled() .keyboardType(.alphabet) .frame(maxWidth: .infinity) .focused($focusedField, equals: ._name) .submitLabel( displayContext == .addition ? .next : .done) + .onSubmit(of: .text) { + if club.acronym.isEmpty { + club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) + } + + if displayContext == .edition && city.isEmpty == true { + focusedField = ._city + } else { + focusedField = nil + } + + } LabeledContent { if acronymMode == .automatic || displayContext == .lockedForEditing { Text(club.acronym) @@ -85,9 +114,7 @@ struct ClubDetailView: View { } } } footer: { - if displayContext == .lockedForEditing { - Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) - } else { + if displayContext != .lockedForEditing { Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut.") } } @@ -132,34 +159,29 @@ struct ClubDetailView: View { .onTapGesture { focusedField = ._zipCode } - } footer: { - if displayContext == .lockedForEditing { - Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) - } } .disabled(displayContext == .lockedForEditing) } - ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt) - - if let padelClubLink = club.shareURL() { - Section { - Link(destination: padelClubLink) { - Text("Accéder au club sur le site Padel Club") - } - } - } - - if let federalLink = club.federalLink() { + let federalLink = club.federalLink() + let padelClubLink = club.shareURL() + if federalLink != nil || padelClubLink != nil { Section { - LabeledContent("Code Club") { - Text(club.code ?? "") - } - LabeledContent("Ville") { - Text(club.city ?? "") + if let federalLink { + LabeledContent("Code Club") { + Text(club.code ?? "") + } + LabeledContent("Ville") { + Text(club.city ?? "") + } + Link(destination: federalLink) { + Text("Accéder au club sur Tenup") + } } - Link(destination: federalLink) { - Text("Voir la fiche du club sur tenup") + if let padelClubLink { + Link(destination: padelClubLink) { + Text("Accéder au club sur Padel Club") + } } } } @@ -189,20 +211,7 @@ struct ClubDetailView: View { } } - if displayContext == .edition || displayContext == .lockedForEditing { - let isFavorite = club.isFavorite() - Section { - RowButtonView(isFavorite ? "Retirer des favoris" : "Mettre en favori", role: isFavorite ? .destructive : nil) { - if isFavorite { - dataStore.user.clubs.removeAll(where: { $0 == club.id }) - } else { - dataStore.user.clubs.append(club.id) - } - self.dataStore.saveUser() - } - } - } - + ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt, hideLockForEditingMessage: true) if displayContext == .edition { Section { @@ -211,6 +220,7 @@ struct ClubDetailView: View { try dataStore.clubs.deleteById(club.id) dataStore.user.clubs.removeAll(where: { $0 == club.id }) self.dataStore.saveUser() + dismiss() } catch { Logger.error(error) } @@ -245,27 +255,6 @@ struct ClubDetailView: View { } } } - .toolbar { - if focusedField == ._name { - ToolbarItem(placement: .keyboard) { - HStack { - Spacer() - Button("Valider") { - if club.acronym.isEmpty { - club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) - } - - if displayContext == .edition && city.isEmpty == true { - focusedField = ._city - } else { - focusedField = nil - } - } - .buttonStyle(.bordered) - } - } - } - } } } diff --git a/PadelClub/Views/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index 6722175..7d4dc8d 100644 --- a/PadelClub/Views/Club/ClubsView.swift +++ b/PadelClub/Views/Club/ClubsView.swift @@ -27,31 +27,56 @@ struct ClubsView: View { var body: some View { List { - let clubs : [Club] = dataStore.user.clubsObjects(includeCreated: true) - ForEach(clubs) { club in - if let selection { - Button { - selection(club) - dismiss() - } label: { - ClubRowView(club: club, displayContext: .selection) - .frame(maxWidth: .infinity) + let clubs : [Club] = dataStore.user.clubsObjects(includeCreated: false) + Section { + ForEach(clubs) { club in + if let selection { + Button { + selection(club) + dismiss() + } label: { + ClubRowView(club: club, displayContext: .selection) + .frame(maxWidth: .infinity) + } + .contentShape(Rectangle()) + .buttonStyle(.plain) + } else { + NavigationLink { + ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user.id) ? .edition : .lockedForEditing) + } label: { + ClubRowView(club: club) + } } - .contentShape(Rectangle()) - .buttonStyle(.plain) - } else { - NavigationLink { - ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user.id) ? .edition : .lockedForEditing) - } label: { - ClubRowView(club: club) + } + } header: { + Text("Clubs favoris") + } + + let onlyCreatedClubs : [Club] = dataStore.user.createdClubsObjectsNotFavorite() + + if onlyCreatedClubs.isEmpty == false { + Section { + ForEach(onlyCreatedClubs) { club in + if let selection { + Button { + selection(club) + dismiss() + } label: { + ClubRowView(club: club, displayContext: .selection) + .frame(maxWidth: .infinity) + } + .contentShape(Rectangle()) + .buttonStyle(.plain) + } else { + NavigationLink { + ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user.id) ? .edition : .lockedForEditing) + } label: { + ClubRowView(club: club) + } + } } -// .swipeActions(edge: .trailing, allowsFullSwipe: true) { -// Button(role: .destructive) { -// try? dataStore.clubs.delete(instance: club) -// } label: { -// LabelDelete() -// } -// } + } header: { + Text("Clubs créés retirés des favoris") } } } diff --git a/PadelClub/Views/Club/CreateClubView.swift b/PadelClub/Views/Club/CreateClubView.swift index fbd1cda..36ffa1e 100644 --- a/PadelClub/Views/Club/CreateClubView.swift +++ b/PadelClub/Views/Club/CreateClubView.swift @@ -14,16 +14,11 @@ struct CreateClubView: View { var club: Club var selection: ((Club) -> ())? = nil + @State private var validationInProgress: Bool = false + var body: some View { NavigationStack { ClubDetailView(club: club, displayContext: .addition, selection: selection) - .task { - do { - try await dataStore.clubs.loadDataFromServerIfAllowed() - } catch { - Logger.error(error) - } - } .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Annuler", role: .cancel) { @@ -31,10 +26,27 @@ struct CreateClubView: View { } } ToolbarItem(placement: .confirmationAction) { - ButtonValidateView { + if validationInProgress { + ProgressView() + } else { + ButtonValidateView { + validationInProgress = true + } + .disabled(club.isValid == false) + } + } + } + .onChange(of: validationInProgress) { + if validationInProgress { + Task { + do { + try await dataStore.clubs.loadDataFromServerIfAllowed() + } catch { + Logger.error(error) + } let existingOrCreatedClub = Club.findOrCreate(name: club.name, code: club.code, city: club.city, zipCode: club.zipCode) - + //update existing club if rights ok / freshly created club with data input from user if existingOrCreatedClub.hasBeenCreated(by: dataStore.user.id) { existingOrCreatedClub.update(fromClub: club) @@ -53,7 +65,6 @@ struct CreateClubView: View { dismiss() selection?(existingOrCreatedClub) } - .disabled(club.isValid == false) } } } diff --git a/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift b/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift index c7dca61..6385698 100644 --- a/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift +++ b/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift @@ -13,7 +13,8 @@ struct ClubCourtSetupView: View { @Bindable var club: Club let displayContext: DisplayContext @Binding var selectedCourt: Court? - + var hideLockForEditingMessage: Bool = false + @ViewBuilder var body: some View { Section { @@ -41,7 +42,7 @@ struct ClubCourtSetupView: View { // } // } } footer: { - if displayContext == .lockedForEditing { + if displayContext == .lockedForEditing && hideLockForEditingMessage == false { Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) } } diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 35506bd..9d41fb5 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -48,7 +48,9 @@ struct MainView: View { dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }) } - var badgeText: Text? = Store.main.userId == nil ? Text("!").font(.headline) : nil + var badgeText: Text? { + dataStore.user.username.isEmpty ? Text("!").font(.headline) : nil + } var body: some View { TabView(selection: selectedTabHandler) { @@ -67,8 +69,8 @@ struct MainView: View { // PadelClubView() // .tabItem(for: .padelClub) } - .id(Store.main.userId) - .onChange(of: Store.main.userId) { + .id(dataStore.user.id) + .onChange(of: dataStore.user.id) { navigation.path.removeLast(navigation.path.count) } .environmentObject(dataStore) diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 2d19134..f1ab5ca 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -109,7 +109,7 @@ struct UmpireView: View { LabeledContent { Text(dataStore.user.clubs.count.formatted()) } label: { - Label("Mes clubs", systemImage: "house.and.flag") + Label("Clubs favoris", systemImage: "house.and.flag") } } } footer: { diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index f48afd9..97b47b8 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Combine enum CashierDestination: Identifiable, Selectable, Equatable { @@ -20,8 +21,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable { var id: String { switch self { - case .summary, .all: + case .summary: return String(describing: self) + case .all(let tournament): + return tournament.id case .groupStage(let groupStage): return groupStage.id case .bracket(let round): @@ -80,15 +83,19 @@ struct TournamentCashierView: View { var tournament: Tournament @State private var selectedDestination: CashierDestination? @StateObject private var cashierViewModel: CashierViewModel = CashierViewModel() + var allDestinations: [CashierDestination] - func allDestinations() -> [CashierDestination] { + init(tournament: Tournament) { + self.tournament = tournament + var allDestinations : [CashierDestination] = [] let tournamentHasEnded = tournament.hasEnded() if tournamentHasEnded { allDestinations.append(.summary) } - allDestinations.append(.all(tournament)) + let all = CashierDestination.all(tournament) + allDestinations.append(all) let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } allDestinations.append(contentsOf: destinations) @@ -102,50 +109,49 @@ struct TournamentCashierView: View { allDestinations.append(.summary) } - return allDestinations - } + self.allDestinations = allDestinations - init(tournament: Tournament) { - self.tournament = tournament + if tournament.hasEnded() { if tournament.players().anySatisfy({ $0.hasPaid() == false }) == false { _selectedDestination = .init(wrappedValue: .summary) } else { - _selectedDestination = .init(wrappedValue: .all(tournament)) + _selectedDestination = .init(wrappedValue: all) } } else { let gs = tournament.getActiveGroupStage() - if let gs { - _selectedDestination = State(wrappedValue: .groupStage(gs)) - } else if let rs = tournament.getActiveRound(withSeeds: true) { - _selectedDestination = State(wrappedValue: .bracket(rs)) + if let gs, let destination = allDestinations.first(where: { $0.id == gs.id }) { + _selectedDestination = State(wrappedValue: destination) + } else if let rs = tournament.getActiveRound(withSeeds: true), let destination = allDestinations.first(where: { $0.id == rs.id }) { + _selectedDestination = State(wrappedValue: destination) } } } var body: some View { VStack(spacing: 0) { - GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) + GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations, nilDestinationIsValid: true) switch selectedDestination { case .none: CashierSettingsView(tournament: tournament) case .some(let selectedCall): switch selectedCall { case .summary: - CashierDetailView(tournament: tournament) + CashierDetailView(tournament: tournament).id(selectedCall.id) case .groupStage(let groupStage): - CashierView(tournament: tournament, teams: groupStage.teams()) - .environmentObject(cashierViewModel) + CashierView(tournament: tournament, teams: groupStage.teams()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) case .bracket(let round): - CashierView(tournament: tournament, teams: round.seeds()) - .environmentObject(cashierViewModel) + CashierView(tournament: tournament, teams: round.seeds()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) case .all(let tournament): - CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()) - .environmentObject(cashierViewModel) + CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()).id(selectedCall.id) + .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur")) } } } .environment(tournament) + .environmentObject(cashierViewModel) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Encaissement") @@ -155,3 +161,26 @@ struct TournamentCashierView: View { #Preview { TournamentCashierView(tournament: Tournament.mock()) } + +//class DebouncedObject: ObservableObject { +// @Published var searchText: String = "" { +// didSet { +// debounceSearchTextPublisher.send(searchText) +// } +// } +// +// private var debounceSearchTextPublisher = PassthroughSubject() +// private var cancellables = Set() +// +// init() { +// debounceSearchTextPublisher +// .debounce(for: .milliseconds(300), scheduler: RunLoop.main) +// .sink { [weak self] in self?.performSearch(with: $0) } +// .store(in: &cancellables) +// } +// +// private func performSearch(with text: String) { +// // Perform the search with the debounced text +// print("Performing search with text: \(text)") +// } +//}