From b7dbac40bced8903af8abbb3ecfdb54b6fd390ab Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 16 Jul 2024 16:56:06 +0200 Subject: [PATCH] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 12 +- PadelClub/Data/Match.swift | 2 - PadelClub/Data/Round.swift | 2 +- PadelClub/Data/TeamRegistration.swift | 32 +- PadelClub/Data/Tournament.swift | 16 +- .../Components/GroupStageSettingsView.swift | 192 ++++++ .../Views/GroupStage/GroupStageView.swift | 170 +---- ...ew.swift => GroupStagesSettingsView.swift} | 21 +- .../Views/GroupStage/GroupStagesView.swift | 2 +- .../GroupStageTeamReplacementView.swift | 11 +- PadelClub/Views/Team/TeamRowView.swift | 2 +- .../Views/Tournament/Screen/AddTeamView.swift | 69 +- .../Tournament/Screen/BroadcastView.swift | 87 +-- .../Screen/InscriptionManagerView.swift | 640 +++++++++++------- .../Shared/TournamentCellView.swift | 1 + 15 files changed, 723 insertions(+), 536 deletions(-) create mode 100644 PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift rename PadelClub/Views/GroupStage/{GroupStageSettingsView.swift => GroupStagesSettingsView.swift} (91%) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index ddd9264..62aff14 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -137,7 +137,7 @@ FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; }; - FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */; }; + FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */; }; FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; @@ -229,6 +229,7 @@ FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; + FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; }; FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; }; FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; }; @@ -474,7 +475,7 @@ FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImportedPlayer+Extensions.swift"; sourceTree = ""; }; FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHolder.swift; sourceTree = ""; }; FF5D30552BD95B1100F2B93D /* OngoingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingView.swift; sourceTree = ""; }; - FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = ""; }; + FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStagesSettingsView.swift; sourceTree = ""; }; FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = ""; }; FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = ""; }; FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = ""; }; @@ -567,6 +568,7 @@ FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = ""; }; FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = ""; }; FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = ""; }; + FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = ""; }; FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamsCallingView.swift; sourceTree = ""; }; FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = ""; }; FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDatePicker.swift; sourceTree = ""; }; @@ -1166,7 +1168,7 @@ children = ( FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, - FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */, + FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */, FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */, FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3922BE3625200C2E883 /* Shared */, @@ -1211,6 +1213,7 @@ isa = PBXGroup; children = ( FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */, + FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */, ); path = Components; sourceTree = ""; @@ -1641,6 +1644,7 @@ FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */, FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, + FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */, FF025AE52BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift in Sources */, FFC2DCB22BBE75D40046DB9F /* LoserRoundView.swift in Sources */, FF90FC1D2C44FB3E009339B2 /* AddTeamView.swift in Sources */, @@ -1692,7 +1696,7 @@ FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */, - FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */, + FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */, FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */, FF1F4B752BFA00FC000B4573 /* HtmlGenerator.swift in Sources */, FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index fb20842..745e456 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -608,8 +608,6 @@ defer { func canBeStarted(inMatches matches: [Match]) -> Bool { let teams = teamScores guard teams.count == 2 else { return false } - guard hasEnded() == false else { return false } - guard hasStarted() == false else { return false } return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false }) } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 8b6098f..df107cf 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -511,7 +511,7 @@ defer { return seedInterval.localizedLabel(displayStyle) } print("Round pas trouvé", id, parent, index) - return "--" + return "Match de classement" } return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index d38c476..87cf1e7 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -71,6 +71,15 @@ final class TeamRegistration: ModelObject, Storable { // MARK: - + func deleteTeamScores() { + let ts = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id }) + do { + try self.tournamentStore.teamScores.delete(contentOfs: ts) + } catch { + Logger.error(error) + } + } + override func deleteDependencies() throws { let unsortedPlayers = unsortedPlayers() for player in unsortedPlayers { @@ -264,18 +273,29 @@ final class TeamRegistration: ModelObject, Storable { } func resetGroupeStagePosition() { - groupStageObject()?._matches().forEach({ $0.updateTeamScores() }) + if let groupStage { + let matches = self.tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { $0.id } + let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) }) + do { + try tournamentStore.teamScores.delete(contentOfs: teamScores) + } catch { + Logger.error(error) + } + } + //groupStageObject()?._matches().forEach({ $0.updateTeamScores() }) groupStage = nil groupStagePosition = nil } func resetBracketPosition() { - guard let bracketPosition else { return } - guard let tournamentObject = tournamentObject() else { return } - if let match = tournamentObject.match(for: bracketPosition) { - let teamScores = match.teamScores.filter({ $0.teamRegistration != self.id }) - tournamentObject.resetTeamScores(in: bracketPosition, outsideOf: teamScores) + let matches = self.tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id } + let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) }) + do { + try tournamentStore.teamScores.delete(contentOfs: teamScores) + } catch { + Logger.error(error) } + self.bracketPosition = nil } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 09c31c9..79287fa 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1063,7 +1063,7 @@ defer { } } - func registrationIssues() async -> Int { + func registrationIssues() -> Int { let players : [PlayerRegistration] = unsortedPlayers() let selectedTeams : [TeamRegistration] = selectedSortedTeams() let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } @@ -1280,6 +1280,20 @@ defer { } } + func unlockRegistration() { + closedRegistrationDate = nil + let teams = unsortedTeams() + teams.forEach { team in + team.lockedWeight = nil + } + do { + try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } + } + + func updateWeights() { let teams = self.unsortedTeams() teams.forEach { team in diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift new file mode 100644 index 0000000..948dfa6 --- /dev/null +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -0,0 +1,192 @@ +// +// GroupStageSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 16/07/2024. +// + +import SwiftUI +import LeStorage + +struct GroupStageSettingsView: View { + + @Environment(\.dismiss) private var dismiss + @Environment(Tournament.self) private var tournament + + @EnvironmentObject var dataStore: DataStore + + @Bindable var groupStage: GroupStage + @State private var groupStageName: String + @State private var presentConfirmationButton: Bool = false + @State private var size: Int + @State private var courtIndex: Int + + init(groupStage: GroupStage) { + _groupStage = Bindable(groupStage) + _groupStageName = .init(wrappedValue: groupStage.name ?? "") + _size = .init(wrappedValue: groupStage.size) + _courtIndex = .init(wrappedValue: groupStage._matches().first?.courtIndex ?? 0) + } + + var tournamentStore: TournamentStore { + return self.tournament.tournamentStore + } + + var body: some View { + Form { + Section { + TextField("Nom de la poule", text: $groupStageName) + .keyboardType(.alphabet) + .frame(maxWidth: .infinity) + .onSubmit { + groupStageName = groupStageName.trimmed + if groupStageName.isEmpty == false { + groupStage.name = groupStageName + _save() + dismiss() + } + } + } footer: { + if groupStage.name != nil { + HStack { + Spacer() + FooterButtonView("retirer le nom") { + groupStage.name = nil + groupStageName = "" + _save() + } + } + } + } + + Section { + CourtPicker(title: "Terrain dédié", selection: $courtIndex, maxCourt: tournament.courtCount) + RowButtonView("Confirmer", role: .destructive) { + groupStage._matches().forEach { match in + match.setCourt(courtIndex) + } + + do { + try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches()) + } catch { + Logger.error(error) + } + } + } + + Section { + LabeledContent { + StepperView(count: $size, minimum: minimumSize(), maximum: maximumSize()) + } label: { + Text("Taille de la poule") + } + + if presentConfirmationButton { + RowButtonView("Confirmer", role: .destructive, confirmationMessage: "Tous les matchs et les équipes de cette poule seront ré-initialisés") { + let teams = groupStage.teams() + teams.forEach { team in + team.groupStagePosition = nil + team.groupStage = nil + groupStage._matches().forEach({ $0.updateTeamScores() }) + } + do { + try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } + groupStage.size = size + groupStage.buildMatches() + tournament.shouldVerifyGroupStage = true + _save() + presentConfirmationButton = false + } + } + } footer: { + Text("Une poule ne peut pas avoir moins de 3 équipes et plus d'une équipe de différence par rapport aux autres poules.") + } + + Section { + RowButtonView("Retirer tous les horaires", role: .destructive) { + groupStage._matches().forEach { match in + match.startDate = nil + match.endDate = nil + } + + do { + try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches()) + } catch { + Logger.error(error) + } + } + } + + Section { + RowButtonView("Retirer tout le monde", role: .destructive) { + let teams = groupStage.teams() + teams.forEach { team in + team.groupStagePosition = nil + team.groupStage = nil + groupStage._matches().forEach({ $0.updateTeamScores() }) + } + do { + try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } + } + } footer: { + Text("Toutes les équipes seront retirées et les scores des matchs seront perdus.") + } + + Section { + RowButtonView("Recommencer tous les matchs", role: .destructive) { + groupStage.buildMatches() + } + } footer: { + Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.") + } + + } + .onChange(of: size) { + if size != groupStage.size { + presentConfirmationButton = true + } + } + .navigationTitle("Paramètres") + .toolbarBackground(.visible, for: .navigationBar) + } + + private func maximumSize() -> Int { + return 10 +// if groupStage.index == 0 { +// return tournament.teamsPerGroupStage + 1 +// } +// +// if let previousGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index - 1 }), previousGroupStage.size > groupStage.size { +// return tournament.teamsPerGroupStage + 1 +// } +// +// return tournament.teamsPerGroupStage + } + + private func minimumSize() -> Int { + return 3 +// if groupStage.index == tournament.groupStageCount - 1 { +// return max(3, tournament.teamsPerGroupStage - 1) +// } +// +// if let nextGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index + 1 }), nextGroupStage.size < groupStage.size { +// return max(3, tournament.teamsPerGroupStage - 1) +// } +// +// return tournament.teamsPerGroupStage + } + + private func _save() { + do { + try tournamentStore.groupStages.addOrUpdate(instance: groupStage) + } catch { + Logger.error(error) + } + } +} diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index e22963f..1c87665 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -16,8 +16,6 @@ struct GroupStageView: View { @Bindable var groupStage: GroupStage @State private var confirmGroupStageStart: Bool = false @State private var sortingMode: GroupStageSortingMode = .auto - @State private var confirmRemoveAll: Bool = false - @State private var confirmResetMatch: Bool = false let playedMatches: [Match] init(groupStage: GroupStage) { @@ -233,168 +231,22 @@ struct GroupStageView: View { } private func _groupStageMenuView() -> some View { - Menu { - NavigationLink { - GroupStageNameEditionView(groupStage: groupStage) - .environment(tournament) - } label: { - Label("Modifier le nom et la taille", systemImage: "pencil") - } - Button("Retirer tout le monde", role: .destructive) { - confirmRemoveAll = true - } - Button("Recommencer tous les matchs", role: .destructive) { - confirmResetMatch = true - } + NavigationLink { + GroupStageSettingsView(groupStage: groupStage) + .environment(tournament) } label: { - LabelOptions() - } - .confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmRemoveAll, titleVisibility: .visible) { - Button("Oui") { - let teams = groupStage.teams() - teams.forEach { team in - team.groupStagePosition = nil - team.groupStage = nil - groupStage._matches().forEach({ $0.updateTeamScores() }) - } - do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) - } - } - } - .confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) { - Button("Oui") { - groupStage.buildMatches() + Label { + Text("Modifier") + } icon: { + Image(systemName: "gear.circle") + .resizable() + .scaledToFit() + .frame(height: 28) } - } - - } - - private func _save() { - do { - try tournamentStore.groupStages.addOrUpdate(instance: groupStage) - } catch { - Logger.error(error) + .labelStyle(.titleOnly) } } -} - -struct GroupStageNameEditionView: View { - - @Environment(\.dismiss) private var dismiss - @Environment(Tournament.self) private var tournament - - @EnvironmentObject var dataStore: DataStore - @Bindable var groupStage: GroupStage - @State private var groupStageName: String - @State private var presentConfirmationButton: Bool = false - @State private var size: Int - - init(groupStage: GroupStage) { - _groupStage = Bindable(groupStage) - _groupStageName = .init(wrappedValue: groupStage.name ?? "") - _size = .init(wrappedValue: groupStage.size) - } - - var tournamentStore: TournamentStore { - return self.tournament.tournamentStore - } - - var body: some View { - Form { - Section { - TextField("Nom de la poule", text: $groupStageName) - .keyboardType(.alphabet) - .frame(maxWidth: .infinity) - .onSubmit { - groupStageName = groupStageName.trimmed - if groupStageName.isEmpty == false { - groupStage.name = groupStageName - _save() - dismiss() - } - } - } footer: { - if groupStage.name != nil { - HStack { - Spacer() - FooterButtonView("retirer le nom") { - groupStage.name = nil - groupStageName = "" - _save() - } - } - } - } - - Section { - LabeledContent { - StepperView(count: $size, minimum: minimumSize(), maximum: maximumSize()) - } label: { - Text("Taille de la poule") - } - - if presentConfirmationButton { - RowButtonView("Confirmer", role: .destructive, confirmationMessage: "Tous les matchs et les équipes de cette poule seront ré-initialisés") { - let teams = groupStage.teams() - teams.forEach { team in - team.groupStagePosition = nil - team.groupStage = nil - groupStage._matches().forEach({ $0.updateTeamScores() }) - } - do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) - } catch { - Logger.error(error) - } - groupStage.size = size - groupStage.buildMatches() - tournament.shouldVerifyGroupStage = true - _save() - presentConfirmationButton = false - } - } - } footer: { - Text("Vous ne pouvez réduire la taille qu'à partir de la dernière poule et augmentez la taille qu'à partir de la première poule. Une poule ne peut pas avoir moins de 3 équipes et plus d'une équipe de différence par rapport aux autres poules.") - } - - } - .onChange(of: size) { - presentConfirmationButton = true - } - .navigationTitle(groupStage.groupStageTitle()) - .toolbarBackground(.visible, for: .navigationBar) - } - - private func maximumSize() -> Int { - return 10 -// if groupStage.index == 0 { -// return tournament.teamsPerGroupStage + 1 -// } -// -// if let previousGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index - 1 }), previousGroupStage.size > groupStage.size { -// return tournament.teamsPerGroupStage + 1 -// } -// -// return tournament.teamsPerGroupStage - } - - private func minimumSize() -> Int { - return 3 -// if groupStage.index == tournament.groupStageCount - 1 { -// return max(3, tournament.teamsPerGroupStage - 1) -// } -// -// if let nextGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index + 1 }), nextGroupStage.size < groupStage.size { -// return max(3, tournament.teamsPerGroupStage - 1) -// } -// -// return tournament.teamsPerGroupStage - } - private func _save() { do { try tournamentStore.groupStages.addOrUpdate(instance: groupStage) diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift similarity index 91% rename from PadelClub/Views/GroupStage/GroupStageSettingsView.swift rename to PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index 8b48766..6200a90 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -1,5 +1,5 @@ // -// GroupStageSettingsView.swift +// GroupStagesSettingsView.swift // PadelClub // // Created by Razmig Sarkissian on 31/03/2024. @@ -8,7 +8,7 @@ import SwiftUI import LeStorage -struct GroupStageSettingsView: View { +struct GroupStagesSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @@ -110,6 +110,23 @@ struct GroupStageSettingsView: View { // Text("Match de perdant de poules") // } + Section { + RowButtonView("Retirer tous les horaires", role: .destructive) { + let matches = tournament.groupStages().flatMap({ $0._matches() }) + tournament.groupStages().flatMap({ $0._matches() }).forEach { match in + match.startDate = nil + match.endDate = nil + } + + do { + try tournamentStore.matches.addOrUpdate(contentOfs: matches) + } catch { + Logger.error(error) + } + } + } + + if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty == false { Section { menuBuildAllGroupStages diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 4709c0d..bd9a732 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -111,7 +111,7 @@ struct GroupStagesView: View { case .groupStage(let groupStage): GroupStageView(groupStage: groupStage).id(groupStage.id) case nil: - GroupStageSettingsView() + GroupStagesSettingsView() .navigationTitle("Réglages") } } diff --git a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift index df97174..b01901b 100644 --- a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift +++ b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift @@ -134,11 +134,11 @@ struct GroupStageTeamReplacementView: View { @ViewBuilder var body: some View { HStack { - TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight) + TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight, leftLimit: true) Spacer() Image(systemName: "arrowshape.forward.fill") Spacer() - TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight) + TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight, leftLimit: false) } } } @@ -146,13 +146,18 @@ struct GroupStageTeamReplacementView: View { struct TeamRangeSideView: View { let team: TeamRegistration? let playerWeight: Int + let leftLimit: Bool @ViewBuilder var body: some View { if let team, team.weight + playerWeight > 0 { Text((team.weight + playerWeight).formatted()).font(.largeTitle) } else { - Text("Aucune limite") + if leftLimit { + Text("1").font(.largeTitle) + } else { + Text("Non classé").font(.largeTitle) + } } } } diff --git a/PadelClub/Views/Team/TeamRowView.swift b/PadelClub/Views/Team/TeamRowView.swift index 16c75ef..9c25d55 100644 --- a/PadelClub/Views/Team/TeamRowView.swift +++ b/PadelClub/Views/Team/TeamRowView.swift @@ -32,7 +32,7 @@ struct TeamRowView: View { .font(.caption) } else { Text("Pas encore convoquée") - .foregroundStyle(.logoYellow) + .foregroundStyle(.logoRed) .italic() .font(.caption) } diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 1242505..587848c 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -32,7 +32,6 @@ struct AddTeamView: View { @State private var pasteString: String? @State private var selectionSearchField: String? @State private var autoSelect: Bool = false - @State private var teamsHash: Int? @State private var presentationCount: Int = 0 @State private var confirmDuplicate: Bool = false @@ -40,7 +39,7 @@ struct AddTeamView: View { return self.tournament.tournamentStore } - init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration?) { + init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration? = nil) { self.tournament = tournament _editedTeam = .init(wrappedValue: editedTeam) if let team = editedTeam { @@ -63,57 +62,6 @@ struct AddTeamView: View { cancelShouldDismiss = true } } - - // 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) - } - - private func _setHash() async { - #if DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func _setHash", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } - #endif - let selectedSortedTeams = tournament.selectedSortedTeams() - if self.teamsHash == nil, selectedSortedTeams.isEmpty == false { - self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) - } - } - - private func _handleHashDiff() async { - let selectedSortedTeams = tournament.selectedSortedTeams() - let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) - if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) { - self.teamsHash = newHash - if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false { - self.tournament.shouldVerifyBracket = true - self.tournament.shouldVerifyGroupStage = true - - let waitingList = self.tournament.waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true) - waitingList.forEach { team in - if team.bracketPosition != nil || team.groupStagePosition != nil { - team.resetPositions() - } - } - - do { - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: waitingList) - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } - } - } - } var body: some View { _buildingTeamView() @@ -197,12 +145,6 @@ struct AddTeamView: View { var unsortedPlayers: [PlayerRegistration] { tournament.unsortedPlayers() } - - private func _getTeams() { - Task { - await _setHash() - } - } @ViewBuilder private func _managementView() -> some View { @@ -494,15 +436,6 @@ struct AddTeamView: View { _managementView() } } - .onAppear { - _getTeams() - } - .onDisappear { - Task { - await _handleHashDiff() - - } - } .headerProminence(.increased) .onReceive(fetchPlayers.publisher.count()) { _ in // <-- here if let pasteString, count == 2, autoSelect == true { diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index b9e181c..54648cc 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -83,53 +83,63 @@ struct BroadcastView: View { } Section { - let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] - Picker(selection: $pageLink) { - ForEach(links) { pageLink in - Text(pageLink.localizedLabel()).tag(pageLink) + TipView(tournamentTVBroadcastTip) + .tipStyle(tint: nil) + } + + if let url = tournament.shareURL(.clubBroadcast) { + Section { + Link(destination: url) { + Text(url.absoluteString) + } + .contextMenu { + Button("Copier") { + let pasteboard = UIPasteboard.general + pasteboard.string = url.absoluteString + } } - } label: { - Text("Choisir la page à partager") + } header: { + Text("Lien pour la diffusion TV") + } footer: { + Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple ! Disponible même si le tournoi est privée.") } - .pickerStyle(.menu) - actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink)) - } header: { - Text("Lien du tournoi à partager") } Section { - let club = tournament.club() - actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) - actionForURL(title: "Padel Club", url: URLs.main.url) - } header: { - Text("Autres liens") + Toggle(isOn: $tournament.isPrivate) { + Text("Tournoi privé") + } + } footer: { + let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))" + Text(.init(footerString)) } if tournament.isPrivate == false { + Section { - TipView(tournamentTVBroadcastTip) - .tipStyle(tint: nil) - } - - if let url = tournament.shareURL(.clubBroadcast) { - Section { - Link(destination: url) { - Text(url.absoluteString) - } - .contextMenu { - Button("Copier") { - let pasteboard = UIPasteboard.general - pasteboard.string = url.absoluteString - } + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()).tag(pageLink) } - } header: { - Text("Lien pour la diffusion TV") - } footer: { - Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !") + } label: { + Text("Choisir la page à partager") } + .pickerStyle(.menu) + actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink)) + } header: { + Text("Lien du tournoi à partager") } - + Section { + let club = tournament.club() + actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) + actionForURL(title: "Padel Club", url: URLs.main.url) + } header: { + Text("Autres liens") + } + + Section { LabeledContent { if tournament.isTournamentPublished() { @@ -286,15 +296,6 @@ struct BroadcastView: View { } //todo waitinglist & info - - Section { - Toggle(isOn: $tournament.isPrivate) { - Text("Tournoi privé") - } - } footer: { - let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))" - Text(.init(footerString)) - } } } .headerProminence(.increased) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 59bf017..16f2616 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -9,15 +9,15 @@ import SwiftUI import TipKit import LeStorage -let slideToDeleteTip = SlideToDeleteTip() +//let slideToDeleteTip = SlideToDeleteTip() let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() -let fileTip = InscriptionManagerFileInputTip() -let pasteTip = InscriptionManagerPasteInputTip() -let searchTip = InscriptionManagerSearchInputTip() -let createTip = InscriptionManagerCreateInputTip() +//let fileTip = InscriptionManagerFileInputTip() +//let pasteTip = InscriptionManagerPasteInputTip() +//let searchTip = InscriptionManagerSearchInputTip() +//let createTip = InscriptionManagerCreateInputTip() let rankUpdateTip = InscriptionManagerRankUpdateTip() -let padelBeachExportTip = PadelBeachExportTip() -let padelBeachImportTip = PadelBeachImportTip() +//let padelBeachExportTip = PadelBeachExportTip() +//let padelBeachImportTip = PadelBeachImportTip() struct InscriptionManagerView: View { @@ -26,11 +26,6 @@ struct InscriptionManagerView: View { @EnvironmentObject var networkMonitor: NetworkMonitor @Environment(\.dismiss) var dismiss - @FetchRequest( - sortDescriptors: [], - animation: .default) - private var fetchPlayers: FetchedResults - var tournament: Tournament var cancelShouldDismiss: Bool = false @@ -52,14 +47,11 @@ struct InscriptionManagerView: View { @State private var contactType: ContactType? = nil @State private var sentError: ContactManagerError? = nil @State private var showSubscriptionView: Bool = false - @State private var registrationIssues: Int? = nil - @State private var sortedTeams: [TeamRegistration] = [] - @State private var walkoutTeams: [TeamRegistration] = [] - @State private var unsortedTeamsWithoutWO: [TeamRegistration] = [] - @State private var unsortedPlayers: [PlayerRegistration] = [] - @State private var teamPaste: URL? @State private var confirmDuplicate: Bool = false @State private var presentAddTeamView: Bool = false + @State private var compactMode: Bool = false + @State private var pasteString: String? + var tournamentStore: TournamentStore { return self.tournament.tournamentStore } @@ -95,15 +87,21 @@ struct InscriptionManagerView: View { case all case walkOut case waiting + case bracket + case groupStage func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .all: - return displayStyle == .wide ? "Équipes inscrites / souhaitées" : "Équipes inscrites" + return displayStyle == .wide ? "Équipes inscrites / souhaitées" : "Paires inscrites" + case .bracket: + return displayStyle == .wide ? "En Tableau" : "Tableau" + case .groupStage: + return displayStyle == .wide ? "En Poule" : "Poule" case .walkOut: - return "Forfaits" + return displayStyle == .wide ? "Forfaits" : "Forfait" case .waiting: - return "Liste d'attente" + return displayStyle == .wide ? "Liste d'attente" : "Attente" } } } @@ -112,16 +110,7 @@ struct InscriptionManagerView: View { self.tournament = tournament _currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate) } - - private func _clearScreen() { - teamPaste = nil - unsortedPlayers.removeAll() - walkoutTeams.removeAll() - unsortedTeamsWithoutWO.removeAll() - sortedTeams.removeAll() - registrationIssues = nil - } - + // Function to create a simple hash from a list of IDs private func _simpleHash(ids: [String]) -> Int { // Combine the hash values of each string @@ -133,7 +122,7 @@ struct InscriptionManagerView: View { return _simpleHash(ids: ids1) != _simpleHash(ids: ids2) } - private func _setHash() async { + private func _setHash() { #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { @@ -147,7 +136,7 @@ struct InscriptionManagerView: View { } } - private func _handleHashDiff() async { + private func _handleHashDiff() { let selectedSortedTeams = tournament.selectedSortedTeams() let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) { @@ -200,13 +189,10 @@ struct InscriptionManagerView: View { } } .onAppear { - _getTeams() + _setHash() } .onDisappear { - Task { - await _handleHashDiff() - - } + _handleHashDiff() } .alert("Un problème est survenu", isPresented: messageSentFailed) { Button("OK") { @@ -268,7 +254,7 @@ struct InscriptionManagerView: View { .tint(.master) } .sheet(isPresented: $presentImportView, onDismiss: { - _getTeams() + _setHash() }) { NavigationStack { FileImportView() @@ -276,14 +262,12 @@ struct InscriptionManagerView: View { .tint(.master) } .onChange(of: tournament.prioritizeClubMembers) { - _clearScreen() _save() - _getTeams() + _setHash() } .onChange(of: tournament.teamSorting) { - _clearScreen() _save() - _getTeams() + _setHash() } .onChange(of: currentRankSourceDate) { if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate { @@ -297,26 +281,36 @@ struct InscriptionManagerView: View { .tint(.master) } .sheet(isPresented: $presentAddTeamView, onDismiss: { - editedTeam = nil - _getTeams() + _setHash() }) { NavigationStack { - AddTeamView(tournament: tournament, editedTeam: editedTeam) + AddTeamView(tournament: tournament) } .tint(.master) } - .onChange(of: filterMode) { - _prepareTeams() - } - .onChange(of: sortingMode) { - _prepareTeams() + .sheet(item: $editedTeam, onDismiss: { + _setHash() + }) { editedTeam in + NavigationStack { + AddTeamView(tournament: tournament, editedTeam: editedTeam) + } + .tint(.master) } - .onChange(of: byDecreasingOrdering) { - _prepareTeams() + .sheet(item: $pasteString, onDismiss: { + _setHash() + }) { pasteString in + NavigationStack { + AddTeamView(tournament: tournament, pasteString: pasteString) + } + .tint(.master) } .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { Menu { + Toggle(isOn: $compactMode) { + Text("Vue compact") + } + Divider() Picker(selection: $filterMode) { ForEach(FilterMode.allCases) { Text($0.localizedLabel(.short)).tag($0) @@ -337,6 +331,7 @@ struct InscriptionManagerView: View { } } label: { LabelFilter() + .symbolVariant(filterMode == .all ? .none : .fill) } Menu { if tournament.inscriptionClosed() == false { @@ -372,7 +367,7 @@ struct InscriptionManagerView: View { } } else { Button { - tournament.closedRegistrationDate = nil + tournament.unlockRegistration() _save() } label: { Label("Ré-ouvrir", systemImage: "lock.open") @@ -392,43 +387,44 @@ struct InscriptionManagerView: View { .navigationBarTitleDisplayMode(.inline) } - private func _prepareStats() async { - #if DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func _prepareStats", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } - #endif - - unsortedPlayers = tournament.unsortedPlayers() - walkoutTeams = tournament.walkoutTeams() - unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() - teamPaste = tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short)) + var walkoutTeams: [TeamRegistration] { + tournament.walkoutTeams() } - private func _prepareTeams() { - #if DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } - #endif + var unsortedTeamsWithoutWO: [TeamRegistration] { + tournament.unsortedTeamsWithoutWO() + } + + var teamPaste: URL? { + tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short)) + } + + var unsortedPlayers: [PlayerRegistration] { + tournament.unsortedPlayers() + } + + var sortedTeams: [TeamRegistration] { if filterMode == .waiting { - sortedTeams = tournament.waitingListSortedTeams() + return tournament.waitingListSortedTeams() } else { - sortedTeams = tournament.sortedTeams() + return tournament.sortedTeams() } } - + var filteredTeams: [TeamRegistration] { var teams = sortedTeams - if filterMode == .walkOut { + switch filterMode { + case .walkOut: teams = teams.filter({ $0.walkOut }) + case .bracket: + teams = teams.filter({ $0.inRound() }) + case .groupStage: + teams = teams.filter({ $0.inGroupStage() }) + default: + break } - + if sortingMode == .registrationDate { teams = teams.sorted(by: \.computedRegistrationDate) } @@ -440,53 +436,14 @@ struct InscriptionManagerView: View { } } - private func _getIssues() async { - #if DEBUG_TIME //DEBUGING TIME - let start = Date() - defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func _getIssues", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) - } - #endif - await registrationIssues = tournament.registrationIssues() - } - - private func _getTeams() { - _prepareTeams() - Task { - await _prepareStats() - await _getIssues() - await _setHash() - } - } - private func _teamRegisteredView() -> some View { List { - _informationView() - let selectedSortedTeams = tournament.selectedSortedTeams() - if let closedRegistrationDate = tournament.closedRegistrationDate { - Section { - CloseDatePicker(closedRegistrationDate: closedRegistrationDate) - } footer: { - Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.") - } - - if selectedSortedTeams.isEmpty { - Section { - ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez aucune équipe inscrite avant la date de clôture.")) - } - } - } - + if presentSearch == false { + _informationView() _rankHandlerView() _relatedTips() - Section { - RowButtonView("Compléter la liste") { - presentAddTeamView = true - } - } } let teams = searchField.isEmpty ? filteredTeams : filteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) @@ -503,13 +460,7 @@ struct InscriptionManagerView: View { } RowButtonView("Créer une équipe") { - // Task { - // await MainActor.run { - // fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption()) - // fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] - // pasteString = searchField - // } - // } + pasteString = searchField } RowButtonView("D'accord") { @@ -518,16 +469,40 @@ struct InscriptionManagerView: View { } } } - ForEach(teams) { team in - let teamIndex = team.index(in: sortedTeams) + if compactMode { Section { - TeamDetailView(team: team) + ForEach(teams) { team in + let teamIndex = team.index(in: sortedTeams) + NavigationLink { + _teamCompactTeamEditionView(team) + .environment(tournament) + } label: { + TeamRowView(team: team) + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + _teamDeleteButtonView(team) + } + } } header: { - TeamHeaderView(team: team, teamIndex: filterMode == .waiting ? nil : teamIndex, tournament: tournament, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count) - } footer: { - _teamFooterView(team) + LabeledContent { + Text(teams.count.formatted()) + } label: { + Text("Équipe\(teams.count.pluralSuffix)") + } } .headerProminence(.increased) + } else { + ForEach(teams) { team in + let teamIndex = team.index(in: sortedTeams) + Section { + TeamDetailView(team: team) + } header: { + TeamHeaderView(team: team, teamIndex: filterMode == .waiting ? nil : teamIndex, tournament: tournament, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count) + } footer: { + _teamFooterView(team) + } + .headerProminence(.increased) + } } } .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites")) @@ -535,6 +510,112 @@ struct InscriptionManagerView: View { .autocorrectionDisabled() } + func _teamCompactTeamEditionView(_ team: TeamRegistration) -> some View { + List { + Section { + TeamDetailView(team: team) + } footer: { + FooterButtonView("Copier dans le presse-papier") { + let pasteboard = UIPasteboard.general + pasteboard.string = team.playersPasteData() + } + } + + Section { + NavigationLink { + EditingTeamView(team: team) + .environment(tournament) + } label: { + Text("Éditer une donnée de l'équipe") + } + + NavigationLink { + GroupStageTeamReplacementView(team: team) + .environment(tournament) + } label: { + Text("Chercher à remplacer") + } + + RowButtonView("Modifier la composition de l'équipe") { + editedTeam = team + } + } + + Section { + Toggle(isOn: .init(get: { + return team.wildCardBracket + }, set: { value in + team.resetPositions() + team.wildCardGroupStage = false + team.walkOut = false + team.wildCardBracket = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _setHash() + })) { + Text("Wildcard Tableau") + } + + Toggle(isOn: .init(get: { + return team.wildCardGroupStage + }, set: { value in + team.resetPositions() + team.wildCardBracket = false + team.walkOut = false + team.wildCardGroupStage = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _setHash() + })) { + Text("Wildcard Poule") + } + } + + Section { + Toggle(isOn: .init(get: { + return team.walkOut + }, set: { value in + team.resetPositions() + team.wildCardBracket = false + team.wildCardGroupStage = false + team.walkOut = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _setHash() + })) { + Text("Forfait") + } + + RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") { + team.deleteTeamScores() + do { + try tournamentStore.teamRegistrations.delete(instance: team) + } catch { + Logger.error(error) + } + _setHash() + } + } + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType) + } + } + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle("Édition de l'équipe") + .navigationBarTitleDisplayMode(.inline) + } + @ViewBuilder func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View { Section { @@ -597,6 +678,10 @@ struct InscriptionManagerView: View { switch filterMode { case .all: return unsortedTeamsWithoutWO.count.formatted() + " / " + tournament.teamCount.formatted() + case .bracket: + return tournament.selectedSortedTeams().filter({ $0.inRound() }).count.formatted() + case .groupStage: + return tournament.selectedSortedTeams().filter({ $0.inGroupStage() }).count.formatted() case .walkOut: let wo = walkoutTeams.count.formatted() return wo @@ -609,30 +694,104 @@ struct InscriptionManagerView: View { @ViewBuilder private func _informationView() -> some View { Section { - ForEach(FilterMode.allCases) { filterMode in - LabeledContent { - Text(_teamCountForFilterMode(filterMode: filterMode)) + HStack { + VStack(alignment: .leading, spacing: 0) { + Text("Inscriptions").font(.caption) + Text(unsortedTeamsWithoutWO.count.formatted()).font(.largeTitle) + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + .onTapGesture { + self.filterMode = .all + } + + VStack(alignment: .leading, spacing: 0) { + Text("Paires souhaitées").font(.caption) + Text(tournament.teamCount.formatted()).font(.largeTitle) + } + .frame(maxWidth: .infinity) + + Button { + presentAddTeamView = true } label: { - Text(filterMode.localizedLabel()) + Label { + Text("Ajouter une équipe") + } icon: { + Image(systemName: "person.2.fill") + .resizable() + .scaledToFit() + .frame(height:44) + } + .labelStyle(.iconOnly) + .overlay(alignment: .bottomTrailing) { + Image(systemName: "plus.circle.fill") + .foregroundColor(.master) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + } } + .buttonBorderShape(.roundedRectangle) + .buttonStyle(.borderedProminent) + .frame(maxWidth: .infinity) } + .fixedSize(horizontal: false, vertical: false) + .padding(.horizontal, -8) + + HStack { + ForEach([FilterMode.waiting, FilterMode.walkOut, FilterMode.groupStage, FilterMode.bracket]) { filterMode in + + Button { + if self.filterMode == filterMode { + self.filterMode = .all + } else { + self.filterMode = filterMode + } + } label: { + VStack(alignment: .leading, spacing: 0) { + Text(filterMode.localizedLabel(.short)).font(.caption) + Text(_teamCountForFilterMode(filterMode: filterMode)).font(.largeTitle) + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + } + .buttonBorderShape(.roundedRectangle) + .buttonStyle(.borderedProminent) + .tint(self.filterMode == filterMode ? .master : .beige) + } + } + .foregroundStyle(.primary) + .fixedSize(horizontal: false, vertical: false) NavigationLink { InscriptionInfoView() .environment(tournament) } label: { LabeledContent { - if let registrationIssues { - Text(registrationIssues.formatted()) - } else { - ProgressView() - } + Text(tournament.registrationIssues().formatted()) } label: { Text("Problèmes détéctés") } } + + if let closedRegistrationDate = tournament.closedRegistrationDate { + CloseDatePicker(closedRegistrationDate: closedRegistrationDate) + } + } header: { - Text("Statut des inscriptions") + HStack { + Spacer() + FooterButtonView(compactMode ? "passer en affichage détaillée" : "passer en affichage compact") { + compactMode.toggle() + } + Spacer() + } + .textCase(nil) + } footer: { + if tournament.closedRegistrationDate != nil { + Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.") + } } } // @@ -821,119 +980,110 @@ struct InscriptionManagerView: View { Text(formattedRegistrationDate) } Spacer() - _teamMenuOptionView(team) + Menu { + _teamMenuOptionView(team) + } label: { + LabelOptions().labelStyle(.titleOnly) + } } } + + @ViewBuilder private func _teamMenuOptionView(_ team: TeamRegistration) -> some View { - Menu { - Section { - NavigationLink { - GroupStageTeamReplacementView(team: team) - } label: { - Text("Chercher à remplacer") - } - - MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType) - //Divider() - Button("Copier") { - let pasteboard = UIPasteboard.general - pasteboard.string = team.playersPasteData() - } - //Divider() - Button("Changer les joueurs") { - editedTeam = team - presentAddTeamView = true - } - Divider() - NavigationLink { - EditingTeamView(team: team) - .environment(tournament) - } label: { - Text("Éditer une donnée de l'équipe") - } - Divider() - Toggle(isOn: .init(get: { - return team.wildCardBracket - }, set: { value in - _clearScreen() + Section { + NavigationLink { + GroupStageTeamReplacementView(team: team) + } label: { + Text("Chercher à remplacer") + } - Task { - team.resetPositions() - team.wildCardGroupStage = false - team.walkOut = false - team.wildCardBracket = value - do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } - _getTeams() - } - })) { - Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle") + MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType) + //Divider() + Button("Copier") { + let pasteboard = UIPasteboard.general + pasteboard.string = team.playersPasteData() + } + //Divider() + Button("Changer les joueurs") { + editedTeam = team + } + Divider() + NavigationLink { + EditingTeamView(team: team) + .environment(tournament) + } label: { + Text("Éditer une donnée de l'équipe") + } + Divider() + Toggle(isOn: .init(get: { + return team.wildCardBracket + }, set: { value in + team.resetPositions() + team.wildCardGroupStage = false + team.walkOut = false + team.wildCardBracket = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) } + _setHash() + })) { + Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle") + } - Toggle(isOn: .init(get: { - return team.wildCardGroupStage - }, set: { value in - _clearScreen() - - Task { - team.resetPositions() - team.wildCardBracket = false - team.walkOut = false - team.wildCardGroupStage = value - do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } - _getTeams() - } - })) { - Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle") - } - - Divider() - Toggle(isOn: .init(get: { - return team.walkOut - }, set: { value in - _clearScreen() - Task { - team.resetPositions() - team.wildCardBracket = false - team.wildCardGroupStage = false - team.walkOut = value - do { - try tournamentStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) - } - _getTeams() - } - })) { - Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle") + Toggle(isOn: .init(get: { + return team.wildCardGroupStage + }, set: { value in + team.resetPositions() + team.wildCardBracket = false + team.walkOut = false + team.wildCardGroupStage = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) } - Divider() - Button(role: .destructive) { - _clearScreen() - Task { - do { - try tournamentStore.teamRegistrations.delete(instance: team) - } catch { - Logger.error(error) - } - _getTeams() - } - } label: { - LabelDelete() + _setHash() + })) { + Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle") + } + + Divider() + Toggle(isOn: .init(get: { + return team.walkOut + }, set: { value in + team.resetPositions() + team.wildCardBracket = false + team.wildCardGroupStage = false + team.walkOut = value + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) } - + _setHash() + })) { + Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle") + } + Divider() + _teamDeleteButtonView(team) // } header: { // Text(team.teamLabel(.short)) + } + } + + private func _teamDeleteButtonView(_ team: TeamRegistration) -> some View { + Button(role: .destructive) { + team.deleteTeamScores() + do { + try tournamentStore.teamRegistrations.delete(instance: team) + } catch { + Logger.error(error) } + _setHash() } label: { - LabelOptions().labelStyle(.titleOnly) + LabelDelete() } } diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 9b2b0f3..10ef423 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -99,6 +99,7 @@ struct TournamentCellView: View { .resizable() .scaledToFit() .frame(height: 28) + .accessibilityLabel("importer ou ouvrir") .tint(existingTournament != nil ? Color.green : nil) } }