diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 9d9fa18..1d034d6 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1258,12 +1258,15 @@ defer { } } - groupStageLoserBracket()?.playedMatches().forEach({ match in - if match.hasEnded() { - teams.setOrAppend(match.winningTeamId, at: match.index) - teams.setOrAppend(match.losingTeamId, at: match.index + 1) - } - }) + if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() { + groupStageLoserBracketPlayedMatches.forEach({ match in + if match.hasEnded() { + let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count + teams.setOrAppend(match.winningTeamId, at: match.index) + teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount) + } + }) + } let groupStages = groupStages() let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 9672531..d5b13b4 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -293,8 +293,8 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { func pointsRange(first: Int, last: Int, teamsCount: Int) -> String { - let range = [points(for: last - 1, count: teamsCount), - points(for: first - 1, count: teamsCount)] + let range = [points(for: first - 1, count: teamsCount), + points(for: last - 1, count: teamsCount)] return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" } diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index bfc447e..0e148a5 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -9,9 +9,10 @@ import SwiftUI import LeStorage struct GroupStagesView: View { - var tournament: Tournament + @State var tournament: Tournament @State private var selectedDestination: GroupStageDestination? - + @EnvironmentObject var dataStore: DataStore + enum GroupStageDestination: Selectable, Identifiable, Equatable { static func == (lhs: GroupStagesView.GroupStageDestination, rhs: GroupStagesView.GroupStageDestination) -> Bool { lhs.id == rhs.id @@ -67,6 +68,7 @@ struct GroupStagesView: View { return nil } case .loserBracket(let loserBracket): + if loserBracket._matches().isEmpty { return nil } return loserBracket.badgeImage() case .groupStage(let groupStage): return groupStage.badgeImage() @@ -77,9 +79,7 @@ struct GroupStagesView: View { var allMatches: [Match] { tournament.groupStagesMatches() } - - @State private var isEditingLoserBracketGroupStage: Bool - + init(tournament: Tournament) { self.tournament = tournament if tournament.shouldVerifyGroupStage { @@ -92,7 +92,6 @@ struct GroupStagesView: View { _selectedDestination = State(wrappedValue: .groupStage(gs)) } } - _isEditingLoserBracketGroupStage = .init(wrappedValue: tournament.groupStageLoserBracket()?._matches().isEmpty ?? false) } func allDestinations() -> [GroupStageDestination] { @@ -158,7 +157,6 @@ struct GroupStagesView: View { GroupStageView(groupStage: groupStage).id(groupStage.id) case .loserBracket(let loserBracket): LoserBracketFromGroupStageView(loserBracket: loserBracket).id(loserBracket.id) - .environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage) case nil: GroupStagesSettingsView() .navigationTitle("Réglages") diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift index d3a4e09..7e85d61 100644 --- a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -10,32 +10,31 @@ import LeStorage struct LoserBracketFromGroupStageView: View { - @Environment(\.isEditingTournamentSeed) var isEditingTournamentSeed @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel - @State var loserBracket: Round + @State private var isEditingLoserBracketGroupStage: Bool + + init(loserBracket: Round) { + self.loserBracket = loserBracket + _isEditingLoserBracketGroupStage = .init(wrappedValue: loserBracket._matches().isEmpty) + } var tournamentStore: TournamentStore { return self.tournament.tournamentStore } + var displayableMatches: [Match] { + loserBracket.playedMatches().sorted(by: \.index) + } + var body: some View { List { - let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) - - if isEditingTournamentSeed.wrappedValue == true { + if isEditingLoserBracketGroupStage == true && displayableMatches.isEmpty == false { Section { RowButtonView("Ajouter un match", role: .destructive) { - let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 - let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) - match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) - } + _addNewMatch() } } } @@ -43,9 +42,10 @@ struct LoserBracketFromGroupStageView: View { ForEach(displayableMatches) { match in Section { MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + .environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage) } header: { let tournamentTeamCount = tournament.teamCount - let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + 1, teamsCount: tournamentTeamCount) + let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + displayableMatches.filter({ $0.index == match.index }).count, teamsCount: tournamentTeamCount) HStack { Text(match.matchTitle(.wide)) Spacer() @@ -53,7 +53,7 @@ struct LoserBracketFromGroupStageView: View { .font(.caption) } } footer: { - if isEditingTournamentSeed.wrappedValue == true { + if isEditingLoserBracketGroupStage == true { HStack { if match.index > tournament.groupStageLoserBracketsInitialPlace() { FooterButtonView("même place qu'au-dessus") { @@ -80,7 +80,7 @@ struct LoserBracketFromGroupStageView: View { } Section { - if displayableMatches.isEmpty == false && isEditingTournamentSeed.wrappedValue == true { + if displayableMatches.count > 1 && isEditingLoserBracketGroupStage == true { Section { RowButtonView("Effacer tous les matchs", role: .destructive) { _deleteAllMatches() @@ -91,21 +91,49 @@ struct LoserBracketFromGroupStageView: View { } } } + .overlay { + if displayableMatches.isEmpty { + ContentUnavailableView { + Label("Aucun match de classement", systemImage: "figure.tennis") + } description: { + Text("Vous n'avez créé aucun match de classement entre les perdants de poules.") + } actions: { + RowButtonView("Ajouter un match") { + isEditingLoserBracketGroupStage = true + _addNewMatch() + } + .padding(.horizontal) + } + } + } .headerProminence(.increased) .navigationTitle("Classement de poules") .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { - if isEditingTournamentSeed.wrappedValue == true { - isEditingTournamentSeed.wrappedValue = false - } else { - isEditingTournamentSeed.wrappedValue = true + if displayableMatches.isEmpty == false { + Button(isEditingLoserBracketGroupStage == true ? "Valider" : "Modifier") { + if isEditingLoserBracketGroupStage == true { + isEditingLoserBracketGroupStage = false + } else { + isEditingLoserBracketGroupStage = true + } } } } } } + private func _addNewMatch() { + let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 + let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) + match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } + private func _deleteAllMatches() { let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) diff --git a/PadelClub/Views/Match/EditSharingView.swift b/PadelClub/Views/Match/EditSharingView.swift index 33c0116..651e419 100644 --- a/PadelClub/Views/Match/EditSharingView.swift +++ b/PadelClub/Views/Match/EditSharingView.swift @@ -24,17 +24,19 @@ struct EditSharingView: View { func shareMessage(displayRank: Bool, displayTeamName: Bool) -> String { var messageData: [String] = [] - var locAndTime: String = "" - if let courtName = match.courtName() { - locAndTime.append("\(courtName)") - } - - if let startDate = match.startDate { - locAndTime = locAndTime + " à " + startDate.formattedAsHourMinute() - } - - if locAndTime.isEmpty == false { - messageData.append(locAndTime) + if match.hasEnded() == false { + var locAndTime: String? + if let courtName = match.courtName() { + locAndTime = "\(courtName)" + } + + if let startDate = match.startDate { + locAndTime = [locAndTime, startDate.formattedAsHourMinute()].compactMap({ $0 }).joined(separator: " à ") + } + + if let locAndTime, locAndTime.isEmpty == false { + messageData.append(locAndTime) + } } if let tournament = match.currentTournament() { @@ -52,6 +54,8 @@ struct EditSharingView: View { let players = "\(labelOne)\ncontre\n\(labelTwo)" messageData.append(players) + messageData.append(match.scoreLabel()) + return messageData.joined(separator: "\n") } diff --git a/PadelClub/Views/Match/MatchRowView.swift b/PadelClub/Views/Match/MatchRowView.swift index 3c486a8..954da7f 100644 --- a/PadelClub/Views/Match/MatchRowView.swift +++ b/PadelClub/Views/Match/MatchRowView.swift @@ -9,7 +9,8 @@ import SwiftUI struct MatchRowView: View { - var match: Match + @EnvironmentObject var dataStore: DataStore + @State var match: Match let matchViewStyle: MatchViewStyle var title: String? = nil diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 2b3ce78..1998374 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -214,6 +214,14 @@ struct MatchSetupView: View { } catch { Logger.error(error) } + } else if match.isLoserBracket { + if let score = match.teamScore(ofTeam: team) { + do { + try tournamentStore.teamScores.delete(instance: score) + } catch { + Logger.error(error) + } + } } else { match.teamWillBeWalkOut(team) do { diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index 16fd9bf..1e4bda1 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -8,7 +8,8 @@ import SwiftUI struct MatchSummaryView: View { - var match: Match + @EnvironmentObject var dataStore: DataStore + @State var match: Match let matchViewStyle: MatchViewStyle let matchTitle: String let roundTitle: String? diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index aa891f9..a9b143c 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -68,19 +68,34 @@ struct TeamPickerView: View { .toolbarBackground(.visible, for: .navigationBar) .navigationBarTitleDisplayMode(.inline) .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + presentTeamPickerView = false + } + } ToolbarItem(placement: .topBarTrailing) { - Picker(selection: $sortOrder) { - Label("Poids", systemImage: "arrow.up").tag(SortOrder.ascending) - .labelStyle(.titleAndIcon) - Label("Poids", systemImage: "arrow.down").tag(SortOrder.descending) - .labelStyle(.titleAndIcon) + Menu { + Section { + Picker(selection: $sortOrder) { + Label("Trier les équipes par poids croissant", systemImage: "chevron.up").tag(SortOrder.ascending) + .labelStyle(.titleAndIcon) + Label("Trier les équipes par poids décroissant", systemImage: "chevron.down").tag(SortOrder.descending) + .labelStyle(.titleAndIcon) + } label: { + Text("Trier les équipes par poids") + } + .pickerStyle(.inline) + } header: { + Text("Trier les équipes par poids") + } } label: { - Label("Trier", systemImage: "arrow.up.arrow.down") + HStack { + Text("Poids") + Image(systemName: sortOrder == .ascending ? "chevron.up" : "chevron.down") + } } - .pickerStyle(.menu) } } - .headerProminence(.increased) } .tint(.master) }