From b4e8a0960393e104e50854c20f83fc2954dc2f60 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 22 May 2024 10:50:59 +0200 Subject: [PATCH] add final ranking fix stuff on teams update --- PadelClub/Data/TeamRegistration.swift | 16 +++++ PadelClub/Data/Tournament.swift | 28 ++++++-- .../GroupStage/GroupStageSettingsView.swift | 10 +++ .../Views/GroupStage/GroupStagesView.swift | 4 +- .../Navigation/Ongoing/OngoingView.swift | 15 +---- PadelClub/Views/Round/RoundSettingsView.swift | 10 +++ PadelClub/Views/Round/RoundsView.swift | 6 +- .../Shared/SelectablePlayerListView.swift | 27 ++++---- .../Screen/InscriptionManagerView.swift | 31 +++++++++ .../Screen/TableStructureView.swift | 1 - .../Screen/TournamentRankView.swift | 50 +++++++++++++- .../Tournament/TournamentBuildView.swift | 66 ++++++++++--------- .../Views/Tournament/TournamentInitView.swift | 2 - .../Views/Tournament/TournamentView.swift | 2 +- 14 files changed, 199 insertions(+), 69 deletions(-) diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index bf3ecf6..22bee3a 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -33,6 +33,8 @@ class TeamRegistration: ModelObject, Storable { var lockedWeight: Int? var confirmationDate: Date? var qualified: Bool = false + var finalRanking: Int? + var pointsEarned: Int? init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false) { self.tournament = tournament @@ -353,6 +355,8 @@ class TeamRegistration: ModelObject, Storable { case _lockedWeight = "lockedWeight" case _confirmationDate = "confirmationDate" case _qualified = "qualified" + case _finalRanking = "finalRanking" + case _pointsEarned = "pointsEarned" } func encode(to encoder: Encoder) throws { @@ -439,6 +443,18 @@ class TeamRegistration: ModelObject, Storable { } try container.encode(qualified, forKey: ._qualified) + + if let finalRanking { + try container.encode(finalRanking, forKey: ._finalRanking) + } else { + try container.encodeNil(forKey: ._finalRanking) + } + + if let pointsEarned { + try container.encode(pointsEarned, forKey: ._pointsEarned) + } else { + try container.encodeNil(forKey: ._pointsEarned) + } } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 303166a..d1dd8bd 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -49,6 +49,10 @@ class Tournament : ModelObject, Storable { var publishGroupStages: Bool = false var publishBrackets: Bool = false + //local + var shouldVerifyGroupStage: Bool = false + var shouldVerifyBracket: Bool = false + @ObservationIgnored var navigationPath: [Screen] = [] @@ -460,7 +464,7 @@ class Tournament : ModelObject, Storable { } func groupStageTeams() -> [TeamRegistration] { - selectedSortedTeams().filter({ $0.bracketPosition == nil && $0.groupStagePosition != nil }) + selectedSortedTeams().filter({ $0.groupStagePosition != nil }) } func seeds() -> [TeamRegistration] { @@ -939,7 +943,7 @@ class Tournament : ModelObject, Storable { let groupStageTeams = groupStage.teams(true) for (index, team) in groupStageTeams.enumerated() { if team.qualified == false { - let groupStageWidth = max(((index == qualifiedPerGroupStage) ? teamsPerGroupStage - groupStageAdditionalQualified : teamsPerGroupStage) * (index - qualifiedPerGroupStage), 0) + let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0) let _index = baseRank + groupStageWidth + 1 if let existingTeams = teams[_index] { @@ -1125,6 +1129,14 @@ class Tournament : ModelObject, Storable { } func bracketStatus() -> String { + let availableSeeds = availableSeeds() + if availableSeeds.isEmpty == false { + return "placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série" + } + let availableQualifiedTeams = availableQualifiedTeams() + if availableQualifiedTeams.isEmpty == false { + return "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix + } if let round = getActiveRound() { return [round.roundTitle(), round.roundStatus()].joined(separator: " ") } else { @@ -1133,6 +1145,10 @@ class Tournament : ModelObject, Storable { } func groupStageStatus() -> String { + let groupStageTeamsCount = groupStageTeams().count + if groupStageTeamsCount == 0 || groupStageTeamsCount != teamsPerGroupStage * groupStageCount { + return "à faire" + } let runningGroupStages = groupStages().filter({ $0.isRunning() }) if groupStagesAreOver() { return "terminées" } if runningGroupStages.isEmpty { @@ -1152,8 +1168,12 @@ class Tournament : ModelObject, Storable { } func structureDescriptionLocalizedLabel() -> String { - let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil - return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") + if state() == .initial { + return "à valider" + } else { + let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil + return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") + } } func deleteAndBuildEverything() { diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 7d3e95b..1c55364 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -28,6 +28,16 @@ struct GroupStageSettingsView: View { var body: some View { List { + if tournament.shouldVerifyGroupStage { + Section { + RowButtonView("Valider les poules", role: .destructive) { + tournament.shouldVerifyGroupStage = false + } + } footer: { + Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.") + } + } + #if DEBUG Section { RowButtonView("delete all group stages") { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 4c25ba7..b09c5dc 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -53,7 +53,9 @@ struct GroupStagesView: View { init(tournament: Tournament) { self.tournament = tournament - if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty { + if tournament.shouldVerifyGroupStage { + _selectedDestination = State(wrappedValue: nil) + } else if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty { _selectedDestination = State(wrappedValue: nil) } else { let gs = tournament.getActiveGroupStage() diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index 7bcaed7..9d7cb90 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -26,21 +26,8 @@ struct OngoingView: View { List { ForEach(matches) { match in Section { - MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + MatchRowView(match: match, matchViewStyle: .standardStyle) } header: { - HStack { - if let roundTitle = match.roundTitle() { - Text(roundTitle) - } - Text(match.matchTitle(.short)) - Spacer() - if let courtName = match.courtName() { - Spacer() - Text(courtName) - } - } - - } footer: { if let tournament = match.currentTournament() { Text(tournament.tournamentTitle()) } diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 92017b9..59f8eb6 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -14,6 +14,16 @@ struct RoundSettingsView: View { var body: some View { List { + if tournament.shouldVerifyBracket { + Section { + RowButtonView("Valider le tableau", role: .destructive) { + tournament.shouldVerifyBracket = false + } + } footer: { + Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.") + } + } + // Section { // RowButtonView("Enabled", role: .destructive) { // let allMatches = tournament._allMatchesIncludingDisabled() diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift index 76df209..08a6c72 100644 --- a/PadelClub/Views/Round/RoundsView.swift +++ b/PadelClub/Views/Round/RoundsView.swift @@ -14,7 +14,11 @@ struct RoundsView: View { init(tournament: Tournament) { self.tournament = tournament - _selectedRound = State(wrappedValue: tournament.getActiveRound()) + if tournament.shouldVerifyBracket { + _selectedRound = State(wrappedValue: nil) + } else { + _selectedRound = State(wrappedValue: tournament.getActiveRound()) + } if tournament.availableSeeds().isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false { _isEditingTournamentSeed = State(wrappedValue: true) } diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index 402cadf..d0bceef 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -109,6 +109,7 @@ struct SelectablePlayerListView: View { } .scrollDismissesKeyboard(.immediately) .navigationBarBackButtonHidden(searchViewModel.allowMultipleSelection) + .toolbarBackground(.visible, for: .bottomBar) // .toolbarRole(searchViewModel.allowMultipleSelection ? .navigationStack : .editor) .interactiveDismissDisabled(searchViewModel.selectedPlayers.isEmpty == false) .navigationTitle(searchViewModel.label(forDataSet: searchViewModel.dataSet)) @@ -158,6 +159,8 @@ struct SelectablePlayerListView: View { if searchViewModel.selectedPlayers.isEmpty && searchViewModel.filterSelectionEnabled { searchViewModel.filterSelectionEnabled = false + } else { + searchViewModel.filterSelectionEnabled = true } } .onChange(of: searchViewModel.dataSet) { @@ -178,25 +181,21 @@ struct SelectablePlayerListView: View { } if searchViewModel.selectedPlayers.isEmpty == false { - ToolbarItem(placement: .bottomBar) { - Button { - searchViewModel.filterSelectionEnabled.toggle() - } label: { - if searchViewModel.filterSelectionEnabled { - Image(systemName: "line.3.horizontal.decrease.circle.fill") - } else { - Image(systemName: "line.3.horizontal.decrease.circle") + ToolbarItem(placement: .topBarTrailing) { + ButtonValidateView { + if let playerSelectionAction { + playerSelectionAction(searchViewModel.selectedPlayers) } + dismiss() } } ToolbarItem(placement: .status) { - Button { - if let playerSelectionAction { - playerSelectionAction(searchViewModel.selectedPlayers) + let count = searchViewModel.selectedPlayers.count + VStack(spacing: 0) { + Text(count.formatted() + " joueur" + count.pluralSuffix + " séléctionné" + count.pluralSuffix).font(.footnote).foregroundStyle(.secondary) + FooterButtonView("\(searchViewModel.filterSelectionEnabled ? "masquer" : "voir") la liste") { + searchViewModel.filterSelectionEnabled.toggle() } - dismiss() - } label: { - Text("Ajouter le" + searchViewModel.selectedPlayers.count.pluralSuffix + " \(searchViewModel.selectedPlayers.count) joueur" + searchViewModel.selectedPlayers.count.pluralSuffix) } } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 63a85d8..d2cf5d8 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -32,6 +32,8 @@ struct InscriptionManagerView: View { @State private var confirmUpdateRank = false @State private var selectionSearchField: String? @State private var autoSelect: Bool = false + @State private var teamsHash: Int? + @State private var presentationCount: Int = 0 let slideToDeleteTip = SlideToDeleteTip() let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() @@ -60,6 +62,19 @@ struct InscriptionManagerView: View { } } + // Function to create a simple hash from a list of IDs + private func _simpleHash(ids: [String]) -> Int { + // Combine the hash values of each string + return ids.reduce(0) { $0 ^ $1.hashValue } + } + + // Function to check if two lists of IDs produce different hashes + private func _areDifferent(ids1: [String], ids2: [String]) -> Bool { + return _simpleHash(ids: ids1) != _simpleHash(ids: ids2) + } + + + var body: some View { VStack(spacing: 0) { _managementView() @@ -71,6 +86,22 @@ struct InscriptionManagerView: View { _teamRegisteredView() } } + .onAppear { + self.presentationCount += 1 + if self.teamsHash == nil { + self.teamsHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) + } + } + .onDisappear { + self.presentationCount -= 1 + if self.presentationCount == 0 { + let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) + if let teamsHash { + self.tournament.shouldVerifyBracket = newHash != teamsHash + self.tournament.shouldVerifyGroupStage = newHash != teamsHash + } + } + } .sheet(isPresented: $isLearningMore) { LearnMoreSheetView(tournament: tournament) .tint(.master) diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index c3e61ee..2a67098 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -203,7 +203,6 @@ struct TableStructureView: View { presentRefreshStructureWarning = true } } - .disabled(updatedElements.isEmpty) .confirmationDialog("Refaire la structure", isPresented: $presentRefreshStructureWarning, actions: { if requirements.allSatisfy({ $0 == .groupStage }) { diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 8dcc0c4..3aa8c4a 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -16,6 +16,47 @@ struct TournamentRankView: View { var body: some View { List { + + Section { + let matchs = tournament.runningMatches(tournament.allMatches()) + let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil }) + LabeledContent { + Text(matchs.count.formatted()) + } label: { + Text("Matchs en cours") + } + + LabeledContent { + if rankingPublished { + Image(systemName: "checkmark") + } else { + Image(systemName: "xmark") + } + } label: { + Text("Classement publié") + } + + RowButtonView("Publier le classement", role: .destructive) { + rankings.keys.sorted().forEach { rank in + if let rankedTeams = rankings[rank] { + rankedTeams.forEach { team in + team.finalRanking = rank + team.pointsEarned = tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount) + } + } + } + _save() + } + } footer: { + FooterButtonView("masquer le classement") { + tournament.unsortedTeams().forEach { team in + team.finalRanking = nil + team.pointsEarned = nil + } + _save() + } + } + let keys = rankings.keys.sorted() ForEach(keys, id: \.self) { key in if let rankedTeams = rankings[key] { @@ -86,7 +127,6 @@ struct TournamentRankView: View { } } } - .listStyle(.grouped) .onAppear { let finalRanks = tournament.finalRanking() finalRanks.keys.sorted().forEach { rank in @@ -99,6 +139,14 @@ struct TournamentRankView: View { .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) } + + private func _save() { + do { + try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) + } catch { + Logger.error(error) + } + } } #Preview { diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 9222004..787a151 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -12,6 +12,42 @@ struct TournamentBuildView: View { @ViewBuilder var body: some View { + if tournament.hasEnded() { + Section { + NavigationLink(value: Screen.rankings) { + Text("Classement") + } + } + } + + Section { + if tournament.groupStageCount > 0 { + NavigationLink(value: Screen.groupStage) { + LabeledContent { + Text(tournament.groupStageStatus()) + } label: { + Text("Poules") + if tournament.shouldVerifyGroupStage { + Text("Veuillez vérifier les poules").foregroundStyle(.logoRed) + } + } + } + } + + if tournament.rounds().isEmpty == false { + NavigationLink(value: Screen.round) { + LabeledContent { + Text(tournament.bracketStatus()) + } label: { + Text("Tableau") + if tournament.shouldVerifyBracket { + Text("Veuillez vérifier la tableau").foregroundStyle(.logoRed) + } + } + } + } + } + Section { if tournament.state() != .finished { NavigationLink(value: Screen.schedule) { @@ -47,36 +83,6 @@ struct TournamentBuildView: View { } } } - - if tournament.hasEnded() { - Section { - NavigationLink(value: Screen.rankings) { - Text("Classement") - } - } - } - - Section { - if tournament.groupStageCount > 0 { - NavigationLink(value: Screen.groupStage) { - LabeledContent { - Text(tournament.groupStageStatus()) - } label: { - Text("Poules") - } - } - } - - if tournament.rounds().isEmpty == false { - NavigationLink(value: Screen.round) { - LabeledContent { - Text(tournament.bracketStatus()) - } label: { - Text("Tableau") - } - } - } - } } } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index d4316ef..89e7678 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -28,7 +28,6 @@ struct TournamentInitView: View { NavigationLink(value: Screen.structure) { LabeledContent { Text(tournament.structureDescriptionLocalizedLabel()) - .tint(.master) } label: { LabelStructure() } @@ -37,7 +36,6 @@ struct TournamentInitView: View { NavigationLink(value: Screen.settings) { LabeledContent { Text(tournament.settingsDescriptionLocalizedLabel()) - .tint(.master) } label: { LabelSettings() } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 904bcce..9f22401 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -143,7 +143,7 @@ struct TournamentView: View { } Divider() - if tournament.state() == .running { + if tournament.state() == .running || tournament.state() == .finished { NavigationLink(value: Screen.event) { Text("Gestion de l'événement") }