diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index bffff07..22a1115 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1065,7 +1065,7 @@ defer { if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty { - setGroupStageTeams(randomize: groupStageSortMode == .random) + setGroupStage(randomize: groupStageSortMode == .random) } } @@ -1417,7 +1417,7 @@ defer { } func missingQualifiedFromGroupStages() -> [TeamRegistration] { - if groupStageAdditionalQualified > 0 { + if groupStageAdditionalQualified > 0 && groupStagesAreOver() { return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in groupStage.teams(true)[safe: qualifiedPerGroupStage] } diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index cde9451..9867553 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -17,7 +17,7 @@ enum URLs: String, Identifiable { //case padelClub = "https://padelclub.app" case tenup = "https://tenup.fft.fr" case padelRules = "https://fft-site.cdn.prismic.io/fft-site/ZgLn3McYqOFdyF7n_LEGUIDEDELACOMPETITIONDEPADEL-MAJDECEMBRE2023.pdf" - + case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf" var id: String { return self.rawValue } var url: URL { diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 43136f3..c82bdab 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -16,11 +16,9 @@ struct GroupStageView: View { @Bindable var groupStage: GroupStage @State private var confirmGroupStageStart: Bool = false @State private var sortingMode: GroupStageSortingMode - let playedMatches: [Match] init(groupStage: GroupStage) { self.groupStage = groupStage - self.playedMatches = groupStage.playedMatches() _sortingMode = .init(wrappedValue: groupStage.hasEnded() ? .score : .weight) } @@ -52,6 +50,7 @@ struct GroupStageView: View { } .headerProminence(.increased) + let playedMatches = groupStage.playedMatches() let runningMatches = groupStage.runningMatches(playedMatches: playedMatches) MatchListView(section: "en cours", matches: groupStage.runningMatches(playedMatches: playedMatches), hideWhenEmpty: true) let availableToStart = groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches) @@ -59,6 +58,12 @@ struct GroupStageView: View { .listRowView(isActive: availableToStart.isEmpty == false, color: .green, hideColorVariation: true) MatchListView(section: "à lancer", matches: groupStage.readyMatches(playedMatches: playedMatches), hideWhenEmpty: true) MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false) + + if playedMatches.isEmpty { + RowButtonView("Créer les matchs de poules") { + groupStage.buildMatches() + } + } } .toolbar { ToolbarItem(placement: .topBarTrailing) { diff --git a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index 2453723..8b3770c 100644 --- a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -20,32 +20,6 @@ struct GroupStagesSettingsView: View { var body: some View { List { - if tournament.groupStageAdditionalQualified > 0 { - let missingQualifiedFromGroupStages = tournament.missingQualifiedFromGroupStages() - Section { - let name = "\((tournament.groupStageAdditionalQualified + 1).ordinalFormatted())" - NavigationLink("Tirage au sort d'un \(name)") { - SpinDrawView(drawees: ["Qualification d'un \(name)"], segments: missingQualifiedFromGroupStages) { results in - results.forEach { drawResult in - missingQualifiedFromGroupStages[drawResult.drawIndex].qualified = true - do { - try self.tournamentStore.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex]) - } catch { - Logger.error(error) - } - } - } - } - .disabled(tournament.moreQualifiedToDraw() == 0 || missingQualifiedFromGroupStages.isEmpty) - } footer: { - if tournament.moreQualifiedToDraw() == 0 { - Text("Aucune équipe supplémentaire à qualifier. Vous pouvez en rajouter en modifier le paramètre dans structure.") - } else if missingQualifiedFromGroupStages.isEmpty { - Text("Aucune équipe supplémentaire à tirer au sort. Attendez la fin des poules.") - } - } - } - if tournament.shouldVerifyGroupStage { Section { let issues = tournament.groupStageTeamPlacementIssue() diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index c2d1cdd..2260f7d 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct GroupStagesView: View { var tournament: Tournament @@ -16,7 +17,7 @@ struct GroupStagesView: View { lhs.id == rhs.id } - case all + case all(Tournament) case groupStage(GroupStage) var id: String { @@ -52,8 +53,12 @@ struct GroupStagesView: View { func badgeImage() -> Badge? { switch self { - case .all: - return nil + case .all(let tournament): + if tournament.groupStageAdditionalQualified > 0 && tournament.moreQualifiedToDraw() > 0 && tournament.missingQualifiedFromGroupStages().isEmpty == false { + return .custom(systemName: "exclamationmark.circle.fill", color: .logoBackground) + } else { + return nil + } case .groupStage(let groupStage): return groupStage.badgeImage() } @@ -80,7 +85,7 @@ struct GroupStagesView: View { } func allDestinations() -> [GroupStageDestination] { - var allDestinations : [GroupStageDestination] = [.all] + var allDestinations : [GroupStageDestination] = [.all(tournament)] let groupStageDestinations : [GroupStageDestination] = tournament.groupStages().map { GroupStageDestination.groupStage($0) } allDestinations.append(contentsOf: groupStageDestinations) return allDestinations @@ -94,6 +99,40 @@ struct GroupStagesView: View { let finishedMatches = tournament.finishedMatches(allMatches) List { + if tournament.groupStageAdditionalQualified > 0 { + let missingQualifiedFromGroupStages = tournament.missingQualifiedFromGroupStages() + Section { + let name = "\((tournament.qualifiedPerGroupStage + 1).ordinalFormatted())" + + NavigationLink { + SpinDrawView(drawees: ["Qualification d'un \(name) de poule"], segments: missingQualifiedFromGroupStages) { results in + results.forEach { drawResult in + missingQualifiedFromGroupStages[drawResult.drawIndex].qualified = true + do { + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex]) + } catch { + Logger.error(error) + } + } + } + } label: { + Label { + Text("Qualifier un \(name) de poule par tirage au sort") + } icon: { + Image(systemName: "exclamationmark.circle.fill") + .foregroundStyle(.logoBackground) + } + } + .disabled(tournament.moreQualifiedToDraw() == 0 || missingQualifiedFromGroupStages.isEmpty) + } footer: { + if tournament.moreQualifiedToDraw() == 0 { + Text("Aucune équipe supplémentaire à qualifier. Vous pouvez en rajouter en modifier le paramètre dans structure.") + } else if missingQualifiedFromGroupStages.isEmpty { + Text("Aucune équipe supplémentaire à tirer au sort. Attendez la fin des poules.") + } + } + } + let runningMatches = tournament.runningMatches(allMatches) MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "prêt à démarrer", matches: tournament.availableToStart(allMatches, in: runningMatches), matchViewStyle: .standardStyle, isExpanded: false) diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index 997cc7c..d2fdfb7 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -14,6 +14,19 @@ struct ToolboxView: View { @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @State private var didResetApiCalls: Bool = false + var lastDataSource: String? { + dataStore.appSettings.lastDataSource + } + + var _mostRecentDateAvailable: Date? { + SourceFileManager.shared.mostRecentDateAvailable + } + + var _lastDataSourceDate: Date? { + guard let lastDataSource else { return nil } + return URL.importDateFormatter.date(from: lastDataSource) + } + var body: some View { @Bindable var navigation = navigation NavigationStack(path: $navigation.toolboxPath) { @@ -27,8 +40,14 @@ struct ToolboxView: View { } SupportButtonView(contentIsUnavailable: false) - + Link(destination: URLs.main.url) { + Text("Accéder au site Padel Club") + } + .contextMenu { + ShareLink(item: URLs.main.url) + } } + #if DEBUG @@ -107,45 +126,51 @@ struct ToolboxView: View { #endif Section { - Link(destination: URLs.main.url) { - Text("Accéder au site Padel Club") + NavigationLink { + PadelClubView() + } label: { + if let _lastDataSourceDate { + LabeledContent { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.green) + } label: { + Text(_lastDataSourceDate.monthYearFormatted) + Text("Classement mensuel utilisé") + } + } else { + LabeledContent { + Image(systemName: "xmark.circle.fill") + .tint(.logoRed) + } label: { + if let _mostRecentDateAvailable { + Text(_mostRecentDateAvailable.monthYearFormatted) + } else { + Text("Aucun") + } + Text("Classement mensuel disponible") + } + } } - } - - Section { NavigationLink { SelectablePlayerListView() } label: { Label("Rechercher un joueur", systemImage: "person.fill.viewfinder") } - } - - Section { + NavigationLink { RankCalculatorView() } label: { Label("Calculateur de points", systemImage: "scalemass") } } - - Section { - NavigationLink { - GlobalSettingsView() - } label: { - Label("Formats de jeu par défaut", systemImage: "megaphone") - } - NavigationLink { - DurationSettingsView() - } label: { - Label("Définir les durées moyennes", systemImage: "deskclock") - } - } footer: { - Text("Vous pouvez définir vos propres estimations de durées de match en fonction du format de jeu.") - } Section { - Link("Accéder au guide de la compétition de la FFT", destination: URLs.padelRules.url) + Link("Accéder au guide de la compétition", destination: URLs.padelRules.url) + Link("Formulaire de décharge des temps de repos", destination: URLs.restingDischarge.url) + } header: { + Text("Documents fédéraux") } + } .overlay(alignment: .bottom) { if didResetApiCalls { diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 649a661..945d2d7 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -18,19 +18,6 @@ struct UmpireView: View { @State private var showSubscriptions: Bool = false // @State var isConnected: Bool = false - - var lastDataSource: String? { - dataStore.appSettings.lastDataSource - } - - var _mostRecentDateAvailable: Date? { - SourceFileManager.shared.mostRecentDateAvailable - } - - var _lastDataSourceDate: Date? { - guard let lastDataSource else { return nil } - return URL.importDateFormatter.date(from: lastDataSource) - } enum UmpireScreen { case login @@ -119,32 +106,20 @@ struct UmpireView: View { Text("Il s'agit des clubs qui sont utilisés pour récupérer les tournois tenup.") } + Section { NavigationLink { - PadelClubView() + GlobalSettingsView() } label: { - if let _lastDataSourceDate { - LabeledContent { - Image(systemName: "checkmark.circle.fill") - .foregroundStyle(.green) - } label: { - Text(_lastDataSourceDate.monthYearFormatted) - Text("Classement mensuel utilisé") - } - } else { - LabeledContent { - Image(systemName: "xmark.circle.fill") - .tint(.logoRed) - } label: { - if let _mostRecentDateAvailable { - Text(_mostRecentDateAvailable.monthYearFormatted) - } else { - Text("Aucun") - } - Text("Classement mensuel disponible") - } - } + Label("Formats de jeu par défaut", systemImage: "megaphone") } + NavigationLink { + DurationSettingsView() + } label: { + Label("Définir les durées moyennes", systemImage: "deskclock") + } + } footer: { + Text("Vous pouvez définir vos propres estimations de durées de match en fonction du format de jeu.") } // Section { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 493163c..b44ef1e 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -532,7 +532,7 @@ struct InscriptionManagerView: View { .swipeActions(edge: .trailing, allowsFullSwipe: true) { _teamDeleteButtonView(team) } - .listRowView(isActive: true, color: team.initialRoundColor() ?? tournament.cutLabelColor(index: teamIndex, teamCount: selectedSortedTeams.count), hideColorVariation: true) + .listRowView(isActive: true, color: team.initialRoundColor() ?? tournament.cutLabelColor(index: teamIndex, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count), hideColorVariation: true) } } header: { if filterMode == .all { diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index ace1ac3..a11e055 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -31,7 +31,10 @@ struct TournamentBuildView: View { } } label: { Text("Poules") - if tournament.shouldVerifyGroupStage { + if tournament.groupStagesAreOver(), tournament.moreQualifiedToDraw() > 0 { + let moreQualifiedToDraw = tournament.moreQualifiedToDraw() + Text("Qualifié\(moreQualifiedToDraw.pluralSuffix) sortant\(moreQualifiedToDraw.pluralSuffix) manquant\(moreQualifiedToDraw.pluralSuffix)").foregroundStyle(.logoRed) + } else if tournament.shouldVerifyGroupStage { Text("Vérifier les poules").foregroundStyle(.logoRed) } else if let range = groupStageStatus?.1 { HStack {