From f691681e94ab46bae03d3f4dde189f5339efcb37 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 27 Sep 2024 11:36:28 +0200 Subject: [PATCH] fix a lot of stuff post PBL --- PadelClub.xcodeproj/project.pbxproj | 8 +- .../Coredata/ImportedPlayer+Extensions.swift | 4 + PadelClub/Data/Federal/PlayerHolder.swift | 1 + PadelClub/Data/GroupStage.swift | 10 +- PadelClub/Data/MatchScheduler.swift | 34 +++--- PadelClub/Data/PlayerRegistration.swift | 10 +- PadelClub/Data/TeamRegistration.swift | 1 + PadelClub/Data/Tournament.swift | 22 +++- PadelClub/Utils/PadelRule.swift | 22 ++-- .../ViewModel/FederalDataViewModel.swift | 27 +++++ .../Calling/Components/MenuWarningView.swift | 2 +- .../Views/Cashier/CashierDetailView.swift | 19 ++- .../Views/Cashier/CashierSettingsView.swift | 67 +++++++++-- PadelClub/Views/Cashier/CashierView.swift | 105 +++++++++++++--- .../Views/Components/FooterButtonView.swift | 16 ++- .../Components/GroupStageTeamView.swift | 4 +- .../Views/GroupStage/GroupStageView.swift | 17 +-- .../GroupStageTeamReplacementView.swift | 2 +- .../Match/Components/MatchDateView.swift | 2 +- .../Match/Components/PlayerBlockView.swift | 16 +-- PadelClub/Views/Match/MatchDetailView.swift | 43 ++++--- .../Navigation/Agenda/CalendarView.swift | 8 +- .../Navigation/Agenda/EventListView.swift | 4 +- .../Agenda/TournamentSubscriptionView.swift | 10 +- .../Views/Planning/PlanningSettingsView.swift | 4 +- .../Components/EditablePlayerView.swift | 18 ++- .../Views/Shared/ImportedPlayerView.swift | 113 +++++++++--------- PadelClub/Views/Team/TeamRowView.swift | 21 ++-- .../Screen/TournamentCashierView.swift | 8 +- .../Shared/TournamentCellView.swift | 17 ++- .../Tournament/TournamentBuildView.swift | 2 +- 31 files changed, 427 insertions(+), 210 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index d6b41f3..4e3d4c8 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3293,7 +3293,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3315,7 +3315,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.14; + MARKETING_VERSION = 1.0.15; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3335,7 +3335,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3356,7 +3356,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.14; + MARKETING_VERSION = 1.0.15; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index ab7d66f..b7679f3 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -122,6 +122,10 @@ extension ImportedPlayer: PlayerHolder { func getProgression() -> Int { return Int(progression) } + + func getComputedRank() -> Int? { + nil + } } fileprivate extension Int { diff --git a/PadelClub/Data/Federal/PlayerHolder.swift b/PadelClub/Data/Federal/PlayerHolder.swift index 07c6860..72949e9 100644 --- a/PadelClub/Data/Federal/PlayerHolder.swift +++ b/PadelClub/Data/Federal/PlayerHolder.swift @@ -27,6 +27,7 @@ protocol PlayerHolder { func isNotFromCurrentDate() -> Bool func getBirthYear() -> Int? func getProgression() -> Int + func getComputedRank() -> Int? } extension PlayerHolder { diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index f99aa95..eadf491 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -266,11 +266,11 @@ final class GroupStage: ModelObject, Storable { case 4: return [2, 3, 1, 4, 5, 0] case 5: - return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] -// return [3, 5, 8, 2, 6, 7, 1, 9, 4, 0] +// return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] + return [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] case 6: - return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] - //return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] + //return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] + return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] default: return [] } @@ -283,7 +283,7 @@ final class GroupStage: ModelObject, Storable { func localizedMatchUpLabel(for matchIndex: Int) -> String { let matchUp = _matchUp(for: matchIndex) if let index = matchUp.first, let index2 = matchUp.last { - return "#\(index + 1) contre #\(index2 + 1)" + return "#\(index + 1) vs #\(index2 + 1)" } else { return "--" } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 6c71fe4..d42973b 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -199,31 +199,29 @@ final class MatchScheduler : ModelObject, Storable { while slots.count < flattenedMatches.count { teamsPerRotation[rotationIndex] = [] freeCourtPerRotation[rotationIndex] = [] - let previousRotationBracketIndexes = slots.filter { $0.rotationIndex == rotationIndex - 1 }.map { ($0.groupIndex, 1) } - let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +) - var rotationMatches = Array(availableMatchs.filter({ match in - teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true - }).prefix(numberOfCourtsAvailablePerRotation)) - - if rotationIndex > 0 { - rotationMatches = rotationMatches.sorted(by: { - if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 { - return $0.groupStageObject!.index < $1.groupStageObject!.index - } else { - return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0 - } - }) - } - (0.. 0 { + rotationMatches = rotationMatches.sorted(by: { + if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 { + return $0.groupStageObject!.index < $1.groupStageObject!.index + } else { + return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0 + } + }) + } + if let first = rotationMatches.first(where: { match in let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration) let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60 let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) - if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable.count { + if courtsUnavailable.contains(courtIndex) { return false } else { return teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true @@ -486,7 +484,7 @@ final class MatchScheduler : ModelObject, Storable { let roundObject = match.roundObject! let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) print("courtsUnavailable \(courtsUnavailable)") - if courtPosition >= availableCourts - courtsUnavailable.count { + if courtsUnavailable.contains(courtPosition) { return false } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 28c1cff..2e966ec 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -220,11 +220,7 @@ final class PlayerRegistration: ModelObject, Storable { return "non classé" + (isMalePlayer() ? "" : "e") } } - - func getRank() -> Int { - computedRank - } - + @MainActor func updateRank(from sources: [CSVParser], lastRank: Int) async throws { if let dataFound = try await history(from: sources) { @@ -586,4 +582,8 @@ extension PlayerRegistration: PlayerHolder { func getProgression() -> Int { 0 } + + func getComputedRank() -> Int? { + computedRank + } } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 1c90fef..ffbcb94 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -208,6 +208,7 @@ final class TeamRegistration: ModelObject, Storable { } func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String { + if let name { return name } return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ") } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 7ef451e..07baa79 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1522,13 +1522,27 @@ defer { return Double(selectedPlayers.filter { $0.hasPaid() }.count) / Double(selectedPlayers.count) } + func presenceStatus() -> Double { + let selectedPlayers = selectedPlayers() + if selectedPlayers.isEmpty { return 0 } + return Double(selectedPlayers.filter { $0.hasArrived }.count) / Double(selectedPlayers.count) + } + typealias TournamentStatus = (label:String, completion: String) func cashierStatus() async -> TournamentStatus { let selectedPlayers = selectedPlayers() - let paid = selectedPlayers.filter({ $0.hasPaid() }) + var filteredPlayers = [PlayerRegistration]() + var wording = "" + if isFree() { + wording = "présent" + filteredPlayers = selectedPlayers.filter({ $0.hasArrived }) + } else { + wording = "encaissé" + filteredPlayers = selectedPlayers.filter({ $0.hasPaid() }) + } // let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" - let label = "\(paid.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs encaissés" - let completion = (Double(paid.count) / Double(selectedPlayers.count)) + let label = "\(filteredPlayers.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs \(wording)\(filteredPlayers.count.pluralSuffix)" + let completion = (Double(filteredPlayers.count) / Double(selectedPlayers.count)) let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) return TournamentStatus(label: label, completion: completionLabel) } @@ -2178,7 +2192,7 @@ extension Tournament: FederalTournamentHolder { } extension Tournament: TournamentBuildHolder { - func buildHolderTitle() -> String { + func buildHolderTitle(_ displayStyle: DisplayStyle) -> String { tournamentTitle(.short) } diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 62a1889..de48b13 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -30,7 +30,7 @@ protocol TournamentBuildHolder: Identifiable { var category: TournamentCategory { get } var level: TournamentLevel { get } var age: FederalTournamentAge { get } - func buildHolderTitle() -> String + func buildHolderTitle(_ displayStyle: DisplayStyle) -> String } struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { @@ -43,29 +43,29 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { // var japFirstName: String? = nil // var japLastName: String? = nil - func buildHolderTitle() -> String { - computedLabel + func buildHolderTitle(_ displayStyle: DisplayStyle) -> String { + computedLabel(displayStyle) } var identifier: String { level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() } - var computedLabel: String { - if age == .senior { return localizedLabel() } - return localizedLabel() + " " + localizedAge + func computedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + if age == .senior { return localizedLabel(displayStyle) } + return localizedLabel(displayStyle) + " " + localizedAge(displayStyle) } func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - level.localizedLevelLabel() + category.localizedLabel(.short) + level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle) } - var localizedTitle: String { - level.localizedLevelLabel() + " " + category.localizedLabel() + func localizedTitle(_ displayStyle: DisplayStyle = .wide) -> String { + level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle) } - var localizedAge: String { - age.tournamentDescriptionLabel + func localizedAge(_ displayStyle: DisplayStyle = .wide) -> String { + age.localizedLabel(displayStyle) } } diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index d4d6d24..71c0cf1 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -97,6 +97,33 @@ class FederalDataViewModel { }) } + func countForTournamentBuilds(from tournaments: [any FederalTournamentHolder]) -> Int { + tournaments.filter({ tournament in + (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) + && + (dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod)) + && + (dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration)) + }) + .flatMap { $0.tournaments } + .filter { + (levels.isEmpty || levels.contains($0.level)) + && + (categories.isEmpty || categories.contains($0.category)) + && + (ageCategories.isEmpty || ageCategories.contains($0.age)) + } + .count + } + + func buildIsValid(_ build: any TournamentBuildHolder) -> Bool { + (levels.isEmpty || levels.contains(build.level)) + && + (categories.isEmpty || categories.contains(build.category)) + && + (ageCategories.isEmpty || ageCategories.contains(build.age)) + } + func isTournamentValidForFilters(_ tournament: Tournament) -> Bool { if tournament.isDeleted { return false } let firstPart = (levels.isEmpty || levels.contains(tournament.level)) diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index 71eacb7..ef094a0 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -124,7 +124,7 @@ struct MenuWarningView: View { @ViewBuilder func _teamActionView(_ team: TeamRegistration) -> some View { - Menu("Toute l'équipe") { + Menu(team.name ?? "Toute l'équipe") { let players = team.players() _actionView(players: players) } diff --git a/PadelClub/Views/Cashier/CashierDetailView.swift b/PadelClub/Views/Cashier/CashierDetailView.swift index 32992c1..074a67e 100644 --- a/PadelClub/Views/Cashier/CashierDetailView.swift +++ b/PadelClub/Views/Cashier/CashierDetailView.swift @@ -89,7 +89,8 @@ struct CashierDetailView: View { let showTournamentTitle: Bool @State private var earnings: Double? = nil @State private var paidCompletion: Double? = nil - + @State private var presence: Double? = nil + var body: some View { Section { LabeledContent { @@ -99,9 +100,15 @@ struct CashierDetailView: View { ProgressView() } } label: { - Text("Encaissement") - if let paidCompletion { - Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) + Text(tournament.isFree() ? "Présence" : "Encaissement") + if tournament.isFree() { + if let presence { + Text(presence.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) + } + } else { + if let paidCompletion { + Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) + } } } CashierDetailDisclosureView(tournament: tournament) @@ -119,6 +126,10 @@ struct CashierDetailView: View { if paidCompletion == nil { paidCompletion = tournament.paidCompletion() } + + if presence == nil { + presence = tournament.presenceStatus() + } } } } diff --git a/PadelClub/Views/Cashier/CashierSettingsView.swift b/PadelClub/Views/Cashier/CashierSettingsView.swift index 908465f..ed5e63b 100644 --- a/PadelClub/Views/Cashier/CashierSettingsView.swift +++ b/PadelClub/Views/Cashier/CashierSettingsView.swift @@ -25,14 +25,12 @@ struct CashierSettingsView: View { var body: some View { List { Section { - RowButtonView("Tout le monde a réglé", role: .destructive) { + RowButtonView("Tout le monde est arrivé", role: .destructive) { for tournament in self.tournaments { let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() }) players.forEach { player in - if player.hasPaid() == false { - player.paymentType = .gift - } + player.hasArrived = true } do { try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) @@ -43,29 +41,72 @@ struct CashierSettingsView: View { } } footer: { - Text("Passe tous les joueurs qui n'ont pas réglé en offert") + Text("Indique tous les joueurs sont là") } - + Section { - RowButtonView("Personne n'a réglé", role: .destructive) { + RowButtonView("Personne n'est là", role: .destructive) { + for tournament in self.tournaments { - let store = tournament.tournamentStore - - let players = tournament.selectedPlayers() + let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() }) players.forEach { player in - player.paymentType = nil + player.hasArrived = false } do { - try store.playerRegistrations.addOrUpdate(contentOfs: players) + try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) } catch { Logger.error(error) } } + } } footer: { - Text("Remet à zéro le type d'encaissement de tous les joueurs") + Text("Indique qu'aucun joueur n'est arrivé") } + if tournaments.count > 1 || tournaments.first?.isFree() == false { + Section { + RowButtonView("Tout le monde a réglé", role: .destructive) { + + for tournament in self.tournaments { + let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() }) + players.forEach { player in + if player.hasPaid() == false { + player.paymentType = .gift + } + } + do { + try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) + } catch { + Logger.error(error) + } + } + + } + } footer: { + Text("Passe tous les joueurs qui n'ont pas réglé en offert") + } + + Section { + RowButtonView("Personne n'a réglé", role: .destructive) { + for tournament in self.tournaments { + let store = tournament.tournamentStore + + let players = tournament.selectedPlayers() + players.forEach { player in + player.paymentType = nil + } + do { + try store.playerRegistrations.addOrUpdate(contentOfs: players) + } catch { + Logger.error(error) + } + } + } + } footer: { + Text("Remet à zéro le type d'encaissement de tous les joueurs") + } + } } } } diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index ce35f7f..b022259 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -57,6 +57,7 @@ class CashierViewModel: ObservableObject { let id: UUID = UUID() @Published var sortOption: SortOption = .callDate @Published var filterOption: FilterOption = .all + @Published var presenceFilterOption: PresenceFilterOption = .all @Published var sortOrder: SortOrder = .ascending @Published var searchText: String = "" @Published var isSearching: Bool = false @@ -69,9 +70,14 @@ class CashierViewModel: ObservableObject { func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { if searchText.isEmpty == false { - sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) && player.contains(searchText) + sortOption.shouldDisplayPlayer(player) + && filterOption.shouldDisplayPlayer(player) + && presenceFilterOption.shouldDisplayPlayer(player) + && player.contains(searchText) } else { - sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) + sortOption.shouldDisplayPlayer(player) + && filterOption.shouldDisplayPlayer(player) + && presenceFilterOption.shouldDisplayPlayer(player) } } @@ -183,6 +189,37 @@ class CashierViewModel: ObservableObject { } } + enum PresenceFilterOption: Int, Identifiable, CaseIterable { + case all + case hasArrived + case hasNotArrived + + var id: Int { self.rawValue } + + func localizedLabel() -> String { + switch self { + case .all: + return "Tous" + case .hasArrived: + return "Présent" + case .hasNotArrived: + return "Absent" + } + } + + func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { + switch self { + case .all: + return true + case .hasArrived: + return player.hasArrived + case .hasNotArrived: + return player.hasArrived == false + + } + } + } + } struct CashierView: View { @@ -201,16 +238,42 @@ struct CashierView: View { _players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() })) } + private func _isFree() -> Bool { + if tournaments.count == 1 { + return tournaments.first?.isFree() == true + } else { + return false + } + } + + private func _editingOptions() -> [EditablePlayerView.PlayerEditingOption] { + if _isFree() { + return [.licenceId, .name, .presence] + } else { + return [.licenceId, .name, .payment] + } + } + var body: some View { List { if cashierViewModel.isSearching == false { Section { - Picker(selection: $cashierViewModel.filterOption) { - ForEach(CashierViewModel.FilterOption.allCases) { filterOption in + Picker(selection: $cashierViewModel.presenceFilterOption) { + ForEach(CashierViewModel.PresenceFilterOption.allCases) { filterOption in Text(filterOption.localizedLabel()).tag(filterOption) } } label: { - Text("Statut du règlement") + Text("Présence") + } + + if _isFree() == false { + Picker(selection: $cashierViewModel.filterOption) { + ForEach(CashierViewModel.FilterOption.allCases) { filterOption in + Text(filterOption.localizedLabel()).tag(filterOption) + } + } label: { + Text("Statut du règlement") + } } Picker(selection: $cashierViewModel.sortOption) { @@ -239,12 +302,12 @@ struct CashierView: View { switch cashierViewModel.sortOption { case .teamRank: - TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1) + TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age: - PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1) + PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) case .callDate: let _teams = teams.filter({ $0.callDate != nil }) - TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1) + TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) } } .onAppear { @@ -279,11 +342,12 @@ struct CashierView: View { @EnvironmentObject var cashierViewModel: CashierViewModel let players: [PlayerRegistration] let displayTournamentTitle: Bool - + let editingOptions: [EditablePlayerView.PlayerEditingOption] + var body: some View { ForEach(players) { player in Section { - EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + EditablePlayerView(player: player, editingOptions: editingOptions) } header: { if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() { Text(tournamentTitle) @@ -301,6 +365,7 @@ struct CashierView: View { @EnvironmentObject var cashierViewModel: CashierViewModel let teams: [TeamRegistration] let displayTournamentTitle: Bool + let editingOptions: [EditablePlayerView.PlayerEditingOption] var body: some View { ForEach(teams) { team in @@ -308,11 +373,17 @@ struct CashierView: View { if players.isEmpty == false { Section { ForEach(players) { player in - EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + EditablePlayerView(player: player, editingOptions: editingOptions) } } header: { - if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { - Text(tournamentTitle) + HStack { + if let name = team.name { + Text(name) + } + if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { + Spacer() + Text(tournamentTitle) + } } } footer: { if let callDate = team.callDate { @@ -329,6 +400,7 @@ struct CashierView: View { @EnvironmentObject var cashierViewModel: CashierViewModel let teams: [TeamRegistration] let displayTournamentTitle: Bool + let editingOptions: [EditablePlayerView.PlayerEditingOption] var body: some View { let groupedTeams = Dictionary(grouping: teams) { team in @@ -343,10 +415,15 @@ struct CashierView: View { if players.isEmpty == false { Section { ForEach(players) { player in - EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) + EditablePlayerView(player: player, editingOptions: editingOptions) } } header: { + if let name = team.name { + Text(name) + } + if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { + Spacer() Text(tournamentTitle) } } footer: { diff --git a/PadelClub/Views/Components/FooterButtonView.swift b/PadelClub/Views/Components/FooterButtonView.swift index a710b18..7730639 100644 --- a/PadelClub/Views/Components/FooterButtonView.swift +++ b/PadelClub/Views/Components/FooterButtonView.swift @@ -11,13 +11,15 @@ fileprivate let defaultConfirmationMessage = "Êtes-vous sûr de vouloir faire c struct FooterButtonView: View { var role: ButtonRole? = nil + var systemImage: String? = nil let title: String let confirmationMessage: String let action: () -> () @State private var askConfirmation: Bool = false - init(_ title: String, role: ButtonRole? = nil, confirmationMessage: String? = nil, action: @escaping () -> Void) { + init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, confirmationMessage: String? = nil, action: @escaping () -> Void) { self.title = title + self.systemImage = systemImage self.action = action self.role = role self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage @@ -31,8 +33,16 @@ struct FooterButtonView: View { action() } } label: { - Text(title) - .underline() + if let systemImage { + HStack { + Text(title) + .underline() + Image(systemName: systemImage).font(.caption) + } + } else { + Text(title) + .underline() + } } .buttonStyle(.borderless) .confirmationDialog("Confirmation", diff --git a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift index 0d87d01..0d94139 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift @@ -40,9 +40,11 @@ struct GroupStageTeamView: View { var body: some View { List { Section { + if let name = team.name { + Text(name).foregroundStyle(.secondary) + } ForEach(team.players()) { player in EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) - .environmentObject(tournament.tournamentStore) } } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 5b04f71..c0b238d 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -125,15 +125,16 @@ struct GroupStageView: View { HStack { VStack(alignment: .leading) { if let teamName = team.name { - Text(teamName).foregroundStyle(.secondary) - } - ForEach(team.players()) { player in - Text(player.playerLabel()).lineLimit(1) - .overlay { - if player.hasArrived && team.isHere() == false { - Color.green.opacity(0.6) + Text(teamName).font(.title3) + } else { + ForEach(team.players()) { player in + Text(player.playerLabel()).lineLimit(1) + .overlay { + if player.hasArrived && team.isHere() == false { + Color.green.opacity(0.6) + } } - } + } } } Spacer() diff --git a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift index b01901b..0ab01e9 100644 --- a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift +++ b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift @@ -54,7 +54,7 @@ struct GroupStageTeamReplacementView: View { Section { Picker(selection: $selectedPlayer) { HStack { - Text("Toute l'équipe") + Text(team.name ?? "Toute l'équipe") Spacer() Text(team.weight.formatted()).bold() } diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index d969252..8034631 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -93,7 +93,7 @@ struct MatchDateView: View { .foregroundStyle(Color.master) .underline() } else { - Text("en attente") + Text("démarrer") .foregroundStyle(Color.master) .underline() } diff --git a/PadelClub/Views/Match/Components/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift index 444aa94..5f5b278 100644 --- a/PadelClub/Views/Match/Components/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -57,19 +57,21 @@ struct PlayerBlockView: View { } if let name = team?.name { - Text(name).foregroundStyle(.secondary) - } - ForEach(names, id: \.self) { name in - Text(name).lineLimit(1) + Text(name).font(.title3) + } else { + ForEach(names, id: \.self) { name in + Text(name).lineLimit(1) + } } } else { ZStack(alignment: .leading) { VStack { if let name = team?.name { - Text(name).foregroundStyle(.secondary) + Text(name).font(.title3) + } else { + Text("longLabelPlayerOne").lineLimit(1) + Text("longLabelPlayerTwo").lineLimit(1) } - Text("longLabelPlayerOne").lineLimit(1) - Text("longLabelPlayerTwo").lineLimit(1) } .opacity(0) Text(_defaultLabel()).foregroundStyle(.secondary).lineLimit(1) diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index bf7c291..235e648 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -107,30 +107,31 @@ struct MatchDetailView: View { } } - let players = self.match.teams().flatMap { $0.players() } - let unpaid = players.filter({ $0.hasPaid() == false }) - - if unpaid.isEmpty == false { - Section { - DisclosureGroup { - ForEach(unpaid) { player in + if self.match.currentTournament()?.isFree() == false { + let players = self.match.teams().flatMap { $0.players() } + let unpaid = players.filter({ $0.hasPaid() == false }) + + if unpaid.isEmpty == false { + Section { + DisclosureGroup { + ForEach(unpaid) { player in + LabeledContent { + PlayerPayView(player: player) + .environmentObject(tournamentStore) + } label: { + Text(player.playerLabel()) + } + } + } label: { LabeledContent { - PlayerPayView(player: player) - .environmentObject(tournamentStore) + Text(unpaid.count.formatted() + " / " + players.count.formatted()) } label: { - Text(player.playerLabel()) + Text("Encaissement manquant") } } - } label: { - LabeledContent { - Text(unpaid.count.formatted() + " / " + players.count.formatted()) - } label: { - Text("Encaissement manquant") - } } } } - menuView } .sheet(isPresented: $showDetails) { @@ -423,9 +424,9 @@ struct MatchDetailView: View { let rotationDuration = match.getDuration() Picker(selection: $startDateSetup) { if match.isReady() { + Text("Tout de suite").tag(MatchDateSetup.now) Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) - Text("Tout de suite").tag(MatchDateSetup.now) } Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration)) Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration)) @@ -464,11 +465,7 @@ struct MatchDetailView: View { Text("Au hasard parmi les libres").tag(MatchFieldSetup.random) Text("Au hasard").tag(MatchFieldSetup.fullRandom) //Text("Premier disponible").tag(MatchFieldSetup.firstAvailable) - if let club = match.currentTournament()?.club() { - ForEach(0.. 1 { - Text("\(value.formatted()) poules commenceront en parallèle") + Text("\(value.formatted()) poules en parallèle") } else { - Text("une poule sera jouer à la fois") + Text("une poule sera jouée à la fois") } } } diff --git a/PadelClub/Views/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index 130b127..b2955fb 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -14,6 +14,7 @@ struct EditablePlayerView: View { case payment case licenceId case name + case presence } @EnvironmentObject var dataStore: DataStore @@ -77,6 +78,13 @@ struct EditablePlayerView: View { Logger.error(error) } } + .onChange(of: player.hasArrived) { + do { + try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player) + } catch { + Logger.error(error) + } + } } @ViewBuilder @@ -91,11 +99,6 @@ struct EditablePlayerView: View { Menu { Button { player.hasArrived.toggle() - do { - try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player) - } catch { - Logger.error(error) - } } label: { Label("Présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle") } @@ -172,6 +175,11 @@ struct EditablePlayerView: View { if editingOptions.contains(.payment) { Spacer() PlayerPayView(player: player) + } else if editingOptions.contains(.presence) { + Spacer() + FooterButtonView(player.hasArrived ? "Présent" : "Sur place ?", role: player.hasArrived ? nil : .cancel, systemImage: player.hasArrived ? "checkmark" : nil) { + player.hasArrived.toggle() + } } } } diff --git a/PadelClub/Views/Shared/ImportedPlayerView.swift b/PadelClub/Views/Shared/ImportedPlayerView.swift index fd99bf8..6de85dd 100644 --- a/PadelClub/Views/Shared/ImportedPlayerView.swift +++ b/PadelClub/Views/Shared/ImportedPlayerView.swift @@ -12,6 +12,9 @@ struct ImportedPlayerView: View { var index: Int? = nil var showFemaleInMaleAssimilation: Bool = false var showProgression: Bool = false + var isAnimation: Bool { + player.getComputedRank() == 0 + } var body: some View { VStack(alignment: .leading) { @@ -39,74 +42,76 @@ struct ImportedPlayerView: View { } .font(.title3) .lineLimit(1) - HStack { - HStack(alignment: .top, spacing: 0) { - Text(player.formattedRank()).italic(player.isAssimilated) - .font(.title3) - .background { - if player.isNotFromCurrentDate() { - UnderlineView() + if isAnimation == false { + HStack { + HStack(alignment: .top, spacing: 0) { + Text(player.formattedRank()).italic(player.isAssimilated) + .font(.title3) + .background { + if player.isNotFromCurrentDate() { + UnderlineView() + } } + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) + .font(.caption) + } + } + + if showProgression, player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } + + if let pts = player.getPoints(), pts > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(pts.formatted()).font(.title3) + Text(" pts").font(.caption) + } + } + + if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { + HStack(alignment: .lastTextBaseline, spacing: 0) { + Text(tournamentPlayed.formatted()).font(.title3) + Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) } - if let rank = player.getRank() { - Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated) - .font(.caption) } } + .lineLimit(1) - if showProgression, player.getProgression() != 0 { + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { HStack(alignment: .top, spacing: 2) { Text("(") - Text(player.getProgression().formatted(.number.sign(strategy: .always()))) - .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) - Text(")") - }.font(.title3) - } - - if let pts = player.getPoints(), pts > 0 { - HStack(alignment: .lastTextBaseline, spacing: 0) { - Text(pts.formatted()).font(.title3) - Text(" pts").font(.caption) + Text(assimilatedAsMaleRank.formatted()) + VStack(alignment: .leading, spacing: 0) { + Text("équivalence") + Text("messieurs") + } + .font(.caption) + Text(")").font(.title3) } } - - if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { - HStack(alignment: .lastTextBaseline, spacing: 0) { - Text(tournamentPlayed.formatted()).font(.title3) - Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) + + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") } } - } - .lineLimit(1) - - if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { - HStack(alignment: .top, spacing: 2) { - Text("(") - Text(assimilatedAsMaleRank.formatted()) - VStack(alignment: .leading, spacing: 0) { - Text("équivalence") - Text("messieurs") - } - .font(.caption) - Text(")").font(.title3) + .font(.caption) + if let clubName = player.clubName { + Text(clubName) + .font(.caption) } - } - - HStack { - Text(player.formattedLicense()) - if let computedAge = player.computedAge { - Text(computedAge.formatted() + " ans") + if let ligueName = player.ligueName { + Text(ligueName) + .font(.caption) } } - .font(.caption) - if let clubName = player.clubName { - Text(clubName) - .font(.caption) - } - if let ligueName = player.ligueName { - Text(ligueName) - .font(.caption) - } } } } diff --git a/PadelClub/Views/Team/TeamRowView.swift b/PadelClub/Views/Team/TeamRowView.swift index 533d4bc..458d4e3 100644 --- a/PadelClub/Views/Team/TeamRowView.swift +++ b/PadelClub/Views/Team/TeamRowView.swift @@ -17,10 +17,6 @@ struct TeamRowView: View { TeamWeightView(team: team, teamPosition: teamPosition) } label: { VStack(alignment: .leading) { - if let name = team.name { - Text(name).foregroundStyle(.secondary) - } - if let groupStage = team.groupStageObject() { HStack { Text(groupStage.groupStageTitle()) @@ -32,13 +28,20 @@ struct TeamRowView: View { Text(round.roundTitle(.wide)) } - if team.players().isEmpty == false { - ForEach(team.players()) { player in - Text(player.playerLabel()) + if let name = team.name { + Text(name).font(.title3) + if team.players().isEmpty { + Text("Aucun joueur") } } else { - Text("Place réservée") - Text("Place réservée") + if team.players().isEmpty == false { + ForEach(team.players()) { player in + Text(player.playerLabel()) + } + } else { + Text("Place réservée") + Text("Place réservée") + } } } if displayCallDate { diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 2353007..b965fb5 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -54,9 +54,15 @@ enum CashierDestination: Identifiable, Selectable, Equatable { case .summary: return nil case .groupStage(let groupStage): + if groupStage.tournamentObject()?.isFree() == true { + return groupStage.unsortedPlayers().filter({ $0.hasArrived == false }).count + } return groupStage.unsortedPlayers().filter({ $0.hasPaid() == false }).count case .bracket(let round): let playerRegistrations: [PlayerRegistration] = round.seeds().flatMap { $0.unsortedPlayers() } + if round.tournamentObject()?.isFree() == true { + return playerRegistrations.filter({ $0.hasArrived == false }).count + } return playerRegistrations.filter({ $0.hasPaid() == false }).count case .all(_): return nil @@ -156,7 +162,7 @@ struct TournamentCashierView: View { .environmentObject(cashierViewModel) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - .navigationTitle("Encaissement") + .navigationTitle(tournament.isFree() ? "Présence" : "Encaissement") } } diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 94a7353..62c22d1 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -11,6 +11,7 @@ import LeStorage struct TournamentCellView: View { @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) private var navigation + @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel let tournament: FederalTournamentHolder // let color: Color = .black @@ -23,11 +24,17 @@ struct TournamentCellView: View { var body: some View { ForEach(tournament.tournaments, id: \.id) { build in - if navigation.agendaDestination == .around, let federalTournament = tournament as? FederalTournament { - NavigationLink { - TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) - } label: { - _buildView(build, existingTournament: event?.existingBuild(build)) + if let federalTournament = tournament as? FederalTournament { + if federalDataViewModel.isFederalTournamentValidForFilters(federalTournament, build: build) { + if navigation.agendaDestination == .around { + NavigationLink { + TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) + } label: { + _buildView(build, existingTournament: event?.existingBuild(build)) + } + } else { + _buildView(build, existingTournament: event?.existingBuild(build)) + } } } else { _buildView(build, existingTournament: event?.existingBuild(build)) diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index a11e055..6da1227 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -185,7 +185,7 @@ struct TournamentBuildView: View { ProgressView() } } label: { - Text("Encaissement") + Text(tournament.isFree() ? "Présence" : "Encaissement") if let tournamentStatus { Text(tournamentStatus.label).lineLimit(1) } else {