From b1db99f2a0961df3f2b1518c6d9ded98369a815e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 17 Apr 2024 20:23:14 +0200 Subject: [PATCH] fix call view fix schedule view clean up --- PadelClub.xcodeproj/project.pbxproj | 8 ++ PadelClub/Data/GroupStage.swift | 17 ++- PadelClub/Data/PlayerRegistration.swift | 6 +- PadelClub/Data/Round.swift | 4 + PadelClub/Data/TeamRegistration.swift | 4 +- PadelClub/Data/Tournament.swift | 24 ++++ PadelClub/ViewModel/AgendaDestination.swift | 4 + PadelClub/ViewModel/Selectable.swift | 1 + .../Views/Calling/GroupStageCallingView.swift | 23 ++-- .../GenericDestinationPickerView.swift | 13 +- .../Views/Components/RowButtonView.swift | 81 ++++++++----- .../Views/GroupStage/GroupStagesView.swift | 4 + PadelClub/Views/Navigation/MainView.swift | 10 +- .../GroupStageScheduleEditorView.swift | 55 +++++++-- .../LoserRoundScheduleEditorView.swift | 94 ++++++++++++--- .../Planning/MatchScheduleEditorView.swift | 31 ++++- .../Views/Planning/PlanningSettingsView.swift | 51 ++++---- .../Planning/RoundScheduleEditorView.swift | 39 +++++- PadelClub/Views/Player/PlayerDetailView.swift | 114 ++++++++++++++++++ PadelClub/Views/Round/LoserRoundsView.swift | 6 +- PadelClub/Views/Round/RoundView.swift | 4 +- .../Views/Shared/ImportedPlayerView.swift | 13 +- PadelClub/Views/Team/EditingTeamView.swift | 45 +++++++ PadelClub/Views/Team/TeamDetailView.swift | 4 +- .../TournamentFieldsManagerView.swift | 7 +- .../Screen/InscriptionManagerView.swift | 18 ++- .../Screen/TournamentCallView.swift | 44 +++++-- .../Screen/TournamentCashierView.swift | 33 ++++- .../Screen/TournamentScheduleView.swift | 5 + .../Screen/TournamentSettingsView.swift | 19 ++- .../Tournament/TournamentRunningView.swift | 2 + .../Views/Tournament/TournamentView.swift | 2 + .../ViewModifiers/DeferredViewModifier.swift | 13 ++ 33 files changed, 653 insertions(+), 145 deletions(-) create mode 100644 PadelClub/Views/Player/PlayerDetailView.swift create mode 100644 PadelClub/Views/Team/EditingTeamView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index ede2098..dba80b4 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -90,6 +90,8 @@ FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627E2BCF9432000C4809 /* PlayerListView.swift */; }; FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; + FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; + FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; @@ -364,6 +366,8 @@ FF11627E2BCF9432000C4809 /* PlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerListView.swift; sourceTree = ""; }; FF1162802BCF945C000C4809 /* TournamentCashierView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentCashierView.swift; sourceTree = ""; }; FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditablePlayerView.swift; sourceTree = ""; }; + FF1162842BD00279000C4809 /* PlayerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDetailView.swift; sourceTree = ""; }; + FF1162862BD004AD000C4809 /* EditingTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditingTeamView.swift; sourceTree = ""; }; FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = ""; }; FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = ""; }; FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = ""; }; @@ -724,6 +728,7 @@ isa = PBXGroup; children = ( FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */, + FF1162842BD00279000C4809 /* PlayerDetailView.swift */, FF089EB02BB001EA00F0AEC7 /* Components */, ); path = Player; @@ -1007,6 +1012,7 @@ FF967D082BAF3D4000A9A3BD /* TeamDetailView.swift */, FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */, FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */, + FF1162862BD004AD000C4809 /* EditingTeamView.swift */, ); path = Team; sourceTree = ""; @@ -1338,6 +1344,7 @@ FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, + FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */, FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */, FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */, FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, @@ -1371,6 +1378,7 @@ FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, + FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */, FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */, C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */, C4A47D7B2B73C0F900ADC637 /* TournamentV2.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index edca863..723cc98 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -208,6 +208,10 @@ class GroupStage: ModelObject, Storable { Store.main.filter { $0.groupStage == self.id } } + func unsortedPlayers() -> [PlayerRegistration] { + unsortedTeams().flatMap({ $0.unsortedPlayers() }) + } + fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) @@ -221,10 +225,13 @@ class GroupStage: ModelObject, Storable { } } + func unsortedTeams() -> [TeamRegistration] { + Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } + } + func teams(_ sortedByScore: Bool = false) -> [TeamRegistration] { - let teams: [TeamRegistration] = Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } if sortedByScore { - return teams.compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in + return unsortedTeams().compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in let predicates: [TeamScoreAreInIncreasingOrder] = [ { $0.wins < $1.wins }, { $0.setDifference < $1.setDifference }, @@ -244,7 +251,7 @@ class GroupStage: ModelObject, Storable { return false }.map({ $0.team }).reversed() } else { - return teams.sorted(by: \TeamRegistration.groupStagePosition!) + return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!) } } @@ -272,4 +279,8 @@ extension GroupStage: Selectable { func badgeValue() -> Int? { runningMatches().count } + + func badgeImage() -> String? { + hasEnded() ? "checkmark.circle.fill" : nil + } } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 26e2224..e912c37 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -165,7 +165,11 @@ class PlayerRegistration: ModelObject, Storable { func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { if let rank, rank > 0 { - return rank.formatted() + if rank != weight { + return weight.formatted() + " (" + rank.formatted() + ")" + } else { + return rank.formatted() + } } else { return "non classé" + (isMalePlayer() ? "" : "e") } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 37a4ab2..0380d30 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -418,4 +418,8 @@ extension Round: Selectable { return playedMatches().filter({ $0.isRunning() }).count } } + + func badgeImage() -> String? { + hasEnded() ? "checkmark.circle.fill" : nil + } } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index b0a9453..65bc7ed 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -285,7 +285,6 @@ class TeamRegistration: ModelObject, Storable { func initialRound() -> Round? { guard let bracketPosition else { return nil } - let matchIndex = RoundRule.matchIndex(fromBracketPosition: bracketPosition) let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == roundIndex }).first } @@ -293,8 +292,7 @@ class TeamRegistration: ModelObject, Storable { func initialMatch() -> Match? { guard let bracketPosition else { return nil } guard let initialRoundObject = initialRound() else { return nil } - let matchIndex = RoundRule.matchIndex(fromBracketPosition: bracketPosition) - return Store.main.filter(isIncluded: { $0.round == initialRoundObject.id && $0.index == matchIndex }).first + return Store.main.filter(isIncluded: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }).first } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index e2be5d8..c57a020 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -528,6 +528,30 @@ class Tournament : ModelObject, Storable { } + func maximumCourtsPerGroupSage() -> Int { + if teamsPerGroupStage > 1 { + return min(teamsPerGroupStage / 2, courtCount) + } else { + return max(1, courtCount) + } + } + + func registrationIssues() -> Int { + let players : [PlayerRegistration] = unsortedPlayers() + let selectedTeams : [TeamRegistration] = selectedSortedTeams() + let callDateIssue : [TeamRegistration] = selectedTeams.filter { isStartDateIsDifferentThanCallDate($0) } + let duplicates : [PlayerRegistration] = duplicates(in: players) + let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == -1 }) + let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) + let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players) + let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) + let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams) + let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) + let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil }) + + return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + } + func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool { guard let callDate = team.callDate else { return false } if let groupStageStartDate = team.groupStageObject()?.startDate { diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index ad20c44..2f05f66 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -55,4 +55,8 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable { nil } } + + func badgeImage() -> String? { + nil + } } diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 5836812..0f656cf 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -10,4 +10,5 @@ import Foundation protocol Selectable { func selectionLabel() -> String func badgeValue() -> Int? + func badgeImage() -> String? } diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 7b003c6..1f8de11 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -48,16 +48,19 @@ struct GroupStageCallingView: View { groupStage.startDate } let keys = times.keys.compactMap { $0 }.sorted() - ForEach(keys, id: \.self) { key in - if let _groupStages = times[key], _groupStages.count > 1 { - let teams = _groupStages.flatMap { $0.teams() } - let callSeeds = teams.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) - Section { - CallView.CallStatusView(count: callSeeds.count, total: teams.count, startDate: key) - } header: { - Text(groupStages.map { $0.groupStageTitle() }.joined(separator: ", ")) - } footer: { - CallView(teams: teams, callDate: key, matchFormat: tournament.groupStageMatchFormat, roundLabel: "poule") + + if keys.count != groupStages.count { + ForEach(keys, id: \.self) { key in + if let _groupStages = times[key] { + let teams = _groupStages.flatMap { $0.teams() } + let callSeeds = teams.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) + Section { + CallView.CallStatusView(count: callSeeds.count, total: teams.count, startDate: key) + } header: { + Text(_groupStages.map { $0.groupStageTitle() }.joined(separator: ", ")) + } footer: { + CallView(teams: teams, callDate: key, matchFormat: tournament.groupStageMatchFormat, roundLabel: "poule") + } } } } diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 943aa37..517aaaf 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -45,9 +45,18 @@ struct GenericDestinationPickerView: View { } .buttonStyle(.plain) .overlay(alignment: .bottomTrailing) { - if let count = destination.badgeValue(), count > 0 { + if let image = destination.badgeImage() { + Image(systemName: image) + .foregroundColor(.green) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 5, y: 5) + } else if let count = destination.badgeValue(), count > 0 { Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") - .foregroundColor(.secondary) + .foregroundColor(.red) .imageScale(.medium) .background ( Color(.systemBackground) diff --git a/PadelClub/Views/Components/RowButtonView.swift b/PadelClub/Views/Components/RowButtonView.swift index b8c3b34..bc7edc5 100644 --- a/PadelClub/Views/Components/RowButtonView.swift +++ b/PadelClub/Views/Components/RowButtonView.swift @@ -14,56 +14,73 @@ struct RowButtonView: View { let title: String var systemImage: String? = nil var image: String? = nil - var animatedProgress: Bool = false let confirmationMessage: String - let action: () -> () + var action: (() -> ())? = nil + var asyncAction: (() async -> ())? = nil + @State private var askConfirmation: Bool = false + @State private var isLoading = false - init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, image: String? = nil, animatedProgress: Bool = false, confirmationMessage: String? = nil, action: @escaping () -> Void) { + init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, image: String? = nil, confirmationMessage: String? = nil, action: @escaping (() -> ())) { self.role = role self.title = title self.systemImage = systemImage self.image = image - self.animatedProgress = animatedProgress self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage self.action = action } - + + init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, image: String? = nil, confirmationMessage: String? = nil, asyncAction: @escaping (() async -> ())) { + self.role = role + self.title = title + self.systemImage = systemImage + self.image = image + self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage + self.asyncAction = asyncAction + } + var body: some View { Button(role: role) { if role == .destructive { askConfirmation = true - } else { + } else if let action { action() + } else if let asyncAction { + isLoading = true + Task { + await asyncAction() + isLoading = false + } } } label: { HStack { - if animatedProgress { - Spacer() - ProgressView() - } else { - if let systemImage { - Image(systemName: systemImage) - .resizable() - .scaledToFit() - .frame(height: 24) - } - if let image { - Image(image) - .resizable() - .scaledToFit() - .frame(width: 32, height: 32) - } - Spacer() - Text(title) - .foregroundColor(.white) - .frame(height: 32) + if let systemImage { + Image(systemName: systemImage) + .resizable() + .scaledToFit() + .frame(height: 24) + } + if let image { + Image(image) + .resizable() + .scaledToFit() + .frame(width: 32, height: 32) } Spacer() + Text(title) + .opacity(isLoading ? 0.0 : 1.0) + .foregroundColor(.white) + .frame(height: 32) + Spacer() } .font(.headline) } - .disabled(animatedProgress) + .overlay { + if isLoading { + ProgressView() + } + } + .disabled(isLoading) .frame(maxWidth: .infinity) .buttonStyle(.borderedProminent) .tint(role == .destructive ? Color.red : Color.master) @@ -73,7 +90,15 @@ struct RowButtonView: View { isPresented: $askConfirmation, titleVisibility: .visible) { Button("OK") { - action() + if let action { + action() + } else if let asyncAction { + isLoading = true + Task { + await asyncAction() + isLoading = false + } + } } Button("Annuler", role: .cancel) {} } message: { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index a5ea044..7dd0d07 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -41,6 +41,10 @@ struct GroupStagesView: View { return groupStage.badgeValue() } } + + func badgeImage() -> String? { + nil + } } init(tournament: Tournament) { diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index d61c9db..3e9384a 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -61,15 +61,7 @@ struct MainView: View { func _activityStatusBoxView() -> some View { _activityStatus() - .font(.title3) - .frame(height: 28) - .padding() - .background { - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(.white) - } - .shadow(radius: 2) - .offset(y: -64) + .toastFormatted() } @ViewBuilder diff --git a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift index b2222a3..023360c 100644 --- a/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift +++ b/PadelClub/Views/Planning/GroupStageScheduleEditorView.swift @@ -10,20 +10,54 @@ import SwiftUI struct GroupStageScheduleEditorView: View { @EnvironmentObject var dataStore: DataStore var groupStage: GroupStage + @State private var startDate: Date + @State private var dateUpdated: Bool = false + + + init(groupStage: GroupStage) { + self.groupStage = groupStage + self._startDate = State(wrappedValue: groupStage.startDate ?? Date()) + } var body: some View { @Bindable var groupStage = groupStage List { Section { MatchFormatPickerView(headerLabel: "Format", matchFormat: $groupStage.matchFormat) - } - - Section { - Text("Modifier l'horaire") - } - - RowButtonView("Convoquer") { - + DatePicker(selection: $startDate) { + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) + } + .onChange(of: startDate) { + dateUpdated = true + } + } header: { + Text(groupStage.groupStageTitle()) + } footer: { + HStack { + Menu { + Text("à demain 9h") + Text("à la prochaine rotation") + Text("à la précédente rotation") + } label: { + Text("décaler") + .underline() + } + Spacer() + + if dateUpdated { + Button { + //todo, faut-il tout décaler ? + groupStage.startDate = startDate + _save() + dateUpdated = false + } label: { + Text("valider la modification") + .underline() + } + } + } + .font(.subheadline) + .buttonStyle(.borderless) } NavigationLink { @@ -35,10 +69,15 @@ struct GroupStageScheduleEditorView: View { .onChange(of: groupStage.matchFormat) { _save() } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) } private func _save() { + let matches = groupStage._matches() + matches.forEach({ $0.matchFormat = groupStage.matchFormat }) + try? dataStore.matches.addOrUpdate(contentOfs: matches) try? dataStore.groupStages.addOrUpdate(instance: groupStage) } } diff --git a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift index 50fc9d6..b580ea7 100644 --- a/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift +++ b/PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift @@ -15,7 +15,8 @@ struct LoserRoundStepScheduleEditorView: View { var matches: [Match] @State private var startDate: Date @State private var matchFormat: MatchFormat - + @State private var dateUpdated: Bool = false + init(round: Round, upperRound: Round) { self.upperRound = upperRound self.round = round @@ -30,16 +31,14 @@ struct LoserRoundStepScheduleEditorView: View { Section { MatchFormatPickerView(headerLabel: "Format", matchFormat: $round.matchFormat) DatePicker(selection: $startDate) { - Text(startDate.formatted(.dateTime.weekday())) + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) } - - RowButtonView("Valider la modification") { - _updateSchedule() + .onChange(of: round.matchFormat) { + dateUpdated = true + } + .onChange(of: startDate) { + dateUpdated = true } - - } header: { - Text(round.selectionLabel()) - } footer: { NavigationLink { List { ForEach(matches) { match in @@ -52,8 +51,36 @@ struct LoserRoundStepScheduleEditorView: View { .navigationTitle(round.selectionLabel()) .environment(tournament) } label: { - Text("voir tous les matchs") + Text("Voir tous les matchs") + } + + } header: { + Text(round.selectionLabel()) + } footer: { + HStack { + Menu { + Text("à demain 9h") + Text("à la prochaine rotation") + Text("à la précédente rotation") + } label: { + Text("décaler") + .underline() + } + Spacer() + + if dateUpdated { + Button { + _updateSchedule() + dateUpdated = false + } label: { + Text("valider la modification") + .underline() + } + } } + .font(.subheadline) + .buttonStyle(.borderless) + } .headerProminence(.increased) } @@ -67,6 +94,9 @@ struct LoserRoundStepScheduleEditorView: View { _save() MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate) + upperRound.loserRounds(forRoundIndex: round.index).forEach({ round in + round.startDate = startDate + }) _save() } @@ -83,7 +113,8 @@ struct LoserRoundScheduleEditorView: View { var loserRounds: [Round] @State private var startDate: Date @State private var matchFormat: MatchFormat - + @State private var dateUpdated: Bool = false + init(upperRound: Round) { self.upperRound = upperRound let _loserRounds = upperRound.loserRounds() @@ -97,13 +128,34 @@ struct LoserRoundScheduleEditorView: View { Section { MatchFormatPickerView(headerLabel: "Format", matchFormat: $matchFormat) DatePicker(selection: $startDate) { - Text(startDate.formatted(.dateTime.weekday())) - } - RowButtonView("Valider la modification") { - _updateSchedule() + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) } } header: { Text("Classement " + upperRound.roundTitle()) + } footer: { + HStack { + Menu { + Text("à demain 9h") + Text("à la prochaine rotation") + Text("à la précédente rotation") + } label: { + Text("décaler") + .underline() + } + Spacer() + + if dateUpdated { + Button { + _updateSchedule() + dateUpdated = false + } label: { + Text("valider la modification") + .underline() + } + } + } + .font(.subheadline) + .buttonStyle(.borderless) } @@ -113,6 +165,12 @@ struct LoserRoundScheduleEditorView: View { } } } + .onChange(of: startDate) { + dateUpdated = true + } + .onChange(of: matchFormat) { + dateUpdated = true + } .headerProminence(.increased) .navigationTitle("Réglages") .toolbarBackground(.visible, for: .navigationBar) @@ -126,12 +184,14 @@ struct LoserRoundScheduleEditorView: View { upperRound.loserRounds().forEach({ round in round.resetRound(updateMatchFormat: matchFormat) }) - + try? dataStore.matches.addOrUpdate(contentOfs: matches) _save() - + MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: upperRound.loserRounds().first?.id, fromMatchId: nil, startDate: startDate) _save() + + upperRound.loserRounds().first?.startDate = startDate } private func _save() { diff --git a/PadelClub/Views/Planning/MatchScheduleEditorView.swift b/PadelClub/Views/Planning/MatchScheduleEditorView.swift index b1af2c8..b5b39cb 100644 --- a/PadelClub/Views/Planning/MatchScheduleEditorView.swift +++ b/PadelClub/Views/Planning/MatchScheduleEditorView.swift @@ -11,6 +11,7 @@ struct MatchScheduleEditorView: View { @Environment(Tournament.self) var tournament: Tournament var match: Match @State private var startDate: Date + @State private var dateUpdated: Bool = false init(match: Match) { self.match = match @@ -20,10 +21,10 @@ struct MatchScheduleEditorView: View { var body: some View { Section { DatePicker(selection: $startDate) { - Text(startDate.formatted(.dateTime.weekday())) + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) } - RowButtonView("Valider la modification") { - _updateSchedule() + .onChange(of: startDate) { + dateUpdated = true } } header: { if let round = match.roundObject { @@ -31,6 +32,30 @@ struct MatchScheduleEditorView: View { } else { Text(match.matchTitle()) } + } footer: { + HStack { + Menu { + Text("à demain 9h") + Text("à la prochaine rotation") + Text("à la précédente rotation") + } label: { + Text("décaler") + .underline() + } + Spacer() + + if dateUpdated { + Button { + _updateSchedule() + dateUpdated = false + } label: { + Text("valider la modification") + .underline() + } + } + } + .font(.subheadline) + .buttonStyle(.borderless) } .headerProminence(.increased) } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 0721e80..601bb67 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -10,7 +10,6 @@ import SwiftUI struct PlanningSettingsView: View { @EnvironmentObject var dataStore: DataStore var tournament: Tournament - @State private var scheduleSetup: Bool = false @State private var randomCourtDistribution: Bool @State private var groupStageCourtCount: Int @State private var upperBracketBreakTime: Bool @@ -20,6 +19,8 @@ struct PlanningSettingsView: View { @State private var upperBracketRotationDifference: Int @State private var timeDifferenceLimit: Double @State private var shouldHandleUpperRoundSlice: Bool + @State private var isScheduling: Bool = false + @State private var schedulingDone: Bool = false init(tournament: Tournament) { self.tournament = tournament @@ -52,10 +53,10 @@ struct PlanningSettingsView: View { } Section { - TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) + TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100) if tournament.groupStages().isEmpty == false { - TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount) + TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage()) } NavigationLink { @@ -102,18 +103,36 @@ struct PlanningSettingsView: View { .disabled(rotationDifferenceIsImportant == false) //timeDifferenceLimit - RowButtonView("Horaire intelligent", role: .destructive) { - _setupSchedule() + schedulingDone = false + await _setupSchedule() + schedulingDone = true } - - if scheduleSetup { - HStack { - Image(systemName: "checkmark") - } + } + + Section { + RowButtonView("Supprimer tous les horaires", role: .destructive) { + let allMatches = tournament.allMatches() + allMatches.forEach({ $0.startDate = nil }) + try? dataStore.matches.addOrUpdate(contentOfs: allMatches) + + let allGroupStages = tournament.groupStages() + allGroupStages.forEach({ $0.startDate = nil }) + try? dataStore.groupStages.addOrUpdate(contentOfs: allGroupStages) + + let allRounds = tournament.allRounds() + allRounds.forEach({ $0.startDate = nil }) + try? dataStore.rounds.addOrUpdate(contentOfs: allRounds) } } } + .overlay(alignment: .bottom) { + if schedulingDone { + Label("Horaires mis à jour", systemImage: "checkmark.circle.fill") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } + } .onChange(of: groupStageCourtCount) { tournament.groupStageCourtCount = groupStageCourtCount _save() @@ -132,7 +151,7 @@ struct PlanningSettingsView: View { } } - private func _setupSchedule() { + private func _setupSchedule() async { let groupStageCourtCount = tournament.groupStageCourtCount ?? 1 let groupStages = tournament.groupStages() let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount @@ -166,13 +185,6 @@ struct PlanningSettingsView: View { let matches = tournament.groupStages().flatMap({ $0._matches() }) matches.forEach({ $0.startDate = nil }) -// var times = Set(groupStages.compactMap { $0.startDate }.filter { $0 >= tournament.startDate } ) -// if times.isEmpty { -// groupStages.forEach({ $0.startDate = tournament.startDate }) -// times.insert(tournament.startDate) -// try? dataStore.groupStages.addOrUpdate(contentOfs: groupStages) -// } - var lastDate : Date = tournament.startDate groupStages.chunked(into: groupStageCourtCount).forEach { groups in groups.forEach({ $0.startDate = lastDate }) @@ -195,9 +207,6 @@ struct PlanningSettingsView: View { try? dataStore.matches.addOrUpdate(contentOfs: matches) matchScheduler.updateSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) - - scheduleSetup = true - } private func _save() { diff --git a/PadelClub/Views/Planning/RoundScheduleEditorView.swift b/PadelClub/Views/Planning/RoundScheduleEditorView.swift index d16ba98..5ced1eb 100644 --- a/PadelClub/Views/Planning/RoundScheduleEditorView.swift +++ b/PadelClub/Views/Planning/RoundScheduleEditorView.swift @@ -13,7 +13,8 @@ struct RoundScheduleEditorView: View { var round: Round @State private var startDate: Date - + @State private var dateUpdated: Bool = false + init(round: Round) { self.round = round self._startDate = State(wrappedValue: round.startDate ?? round.playedMatches().first?.startDate ?? Date()) @@ -25,17 +26,46 @@ struct RoundScheduleEditorView: View { Section { MatchFormatPickerView(headerLabel: "Format", matchFormat: $round.matchFormat) DatePicker(selection: $startDate) { - Text(startDate.formatted(.dateTime.weekday())) + Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline) + } + .onChange(of: round.matchFormat) { + dateUpdated = true + } + .onChange(of: startDate) { + dateUpdated = true } - RowButtonView("Valider la modification") { - _updateSchedule() + } footer: { + HStack { + Menu { + Text("à demain 9h") + Text("à la prochaine rotation") + Text("à la précédente rotation") + } label: { + Text("décaler") + .underline() + } + Spacer() + + if dateUpdated { + Button { + _updateSchedule() + dateUpdated = false + } label: { + Text("valider la modification") + .underline() + } + } } + .font(.subheadline) + .buttonStyle(.borderless) } ForEach(round.playedMatches()) { match in MatchScheduleEditorView(match: match) } } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) } private func _updateSchedule() { @@ -47,6 +77,7 @@ struct RoundScheduleEditorView: View { _save() MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate) + round.startDate = startDate _save() } diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift new file mode 100644 index 0000000..6b0624a --- /dev/null +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -0,0 +1,114 @@ +// +// PlayerDetailView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 17/04/2024. +// + +import SwiftUI + +struct PlayerDetailView: View { + @Environment(Tournament.self) var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + @Bindable var player: PlayerRegistration + @FocusState private var textFieldIsFocus: Bool + + var body: some View { + Form { + Section { + LabeledContent { + TextField("Nom", text: $player.lastName) + .keyboardType(.alphabet) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + } label: { + Text("Nom") + } + + LabeledContent { + TextField("Prénom", text: $player.firstName) + .keyboardType(.alphabet) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + } label: { + Text("Prénom") + } + + PlayerSexPickerView(player: player) + } + + Section { + LabeledContent { + TextField("Rang", value: $player.rank, format: .number) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($textFieldIsFocus) + } label: { + Text("Rang") + } + } header: { + Text("Classement actuel") + } + + if player.isMalePlayer() == false && tournament.tournamentCategory == .men, let rank = player.rank { + Section { + let value = PlayerRegistration.addon(for: rank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) + LabeledContent { + Text(value.formatted()) + } label: { + Text("Valeur à rajouter") + } + LabeledContent { + TextField("Rang", value: $player.weight, format: .number) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($textFieldIsFocus) + } label: { + Text("Poids re-calculé") + } + } header: { + Text("Ré-assimilation") + } footer: { + Text("Calculé en fonction du sexe") + } + } + } + .scrollDismissesKeyboard(.immediately) + .onChange(of: player.sex) { + _save() + } + .onChange(of: player.weight) { + player.team()?.updateWeight() + _save() + } + .onChange(of: player.rank) { + player.setWeight(in: tournament) + player.team()?.updateWeight() + _save() + } + .headerProminence(.increased) + .navigationTitle("Édition") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Valider") { + textFieldIsFocus = false + } + } + } + } + + private func _save() { + try? dataStore.playerRegistrations.addOrUpdate(instance: player) + if let team = player.team() { + try? dataStore.teamRegistrations.addOrUpdate(instance: team) + } + } +} + +#Preview { + PlayerDetailView(player: PlayerRegistration.mock()) +} diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index 9c86850..8a311a3 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -73,8 +73,10 @@ struct LoserRoundView: View { } .headerProminence(.increased) .toolbar { - Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { - isEditingTournamentSeed.wrappedValue.toggle() + ToolbarItem(placement: .topBarTrailing) { + Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { + isEditingTournamentSeed.wrappedValue.toggle() + } } } } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index ffa25a3..c9e0ab2 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -53,7 +53,9 @@ struct RoundView: View { .headerProminence(.increased) .toolbar { ToolbarItem(placement: .topBarTrailing) { - EditButton() + Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { + isEditingTournamentSeed.wrappedValue.toggle() + } } } } diff --git a/PadelClub/Views/Shared/ImportedPlayerView.swift b/PadelClub/Views/Shared/ImportedPlayerView.swift index e010b79..392c64a 100644 --- a/PadelClub/Views/Shared/ImportedPlayerView.swift +++ b/PadelClub/Views/Shared/ImportedPlayerView.swift @@ -29,9 +29,6 @@ struct ImportedPlayerView: View { .foregroundStyle(.secondary) .font(.caption) } - } else if let computedAge = player.computedAge { - Text(computedAge.formatted() + " ans") - .foregroundStyle(.secondary) } } .font(.title3) @@ -61,9 +58,13 @@ struct ImportedPlayerView: View { } } - Text(player.formattedLicense()) - .font(.caption) - + HStack { + Text(player.formattedLicense()) + if let computedAge = player.computedAge { + Text(computedAge.formatted() + " ans") + } + } + .font(.caption) if let clubName = player.clubName { Text(clubName) .font(.caption) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift new file mode 100644 index 0000000..db408eb --- /dev/null +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -0,0 +1,45 @@ +// +// EditingTeamView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 17/04/2024. +// + +import SwiftUI + +struct EditingTeamView: View { + @EnvironmentObject var dataStore: DataStore + var team: TeamRegistration + @State private var registrationDate : Date + + init(team: TeamRegistration) { + self.team = team + _registrationDate = State(wrappedValue: team.registrationDate ?? Date()) + } + + var body: some View { + List { + Section { + DatePicker(registrationDate.formatted(.dateTime.weekday()), selection: $registrationDate) + } header: { + Text("Date d'inscription") + } + } + .onChange(of: registrationDate) { + team.registrationDate = registrationDate + _save() + } + .headerProminence(.increased) + .navigationTitle("Édition") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + + private func _save() { + try? dataStore.teamRegistrations.addOrUpdate(instance: team) + } +} + +#Preview { + EditingTeamView(team: TeamRegistration.mock()) +} diff --git a/PadelClub/Views/Team/TeamDetailView.swift b/PadelClub/Views/Team/TeamDetailView.swift index cff5a51..8325fc2 100644 --- a/PadelClub/Views/Team/TeamDetailView.swift +++ b/PadelClub/Views/Team/TeamDetailView.swift @@ -8,6 +8,7 @@ import SwiftUI struct TeamDetailView: View { + @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore var team: TeamRegistration @@ -17,7 +18,8 @@ struct TeamDetailView: View { } else { ForEach(team.players()) { player in NavigationLink { - Text("Hello wolrd") + PlayerDetailView(player: player) + .environment(tournament) } label: { PlayerView(player: player) } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift index 56bf2d7..5ada9bf 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift @@ -10,18 +10,19 @@ import SwiftUI struct TournamentFieldsManagerView: View { let localizedStringKey: String @Binding var count: Int + let max: Int var body: some View { LabeledContent { - StepperView(count: $count, minimum: 1, maximum: 1_000) + StepperView(count: $count, minimum: 1, maximum: max) } label: { Text(localizedStringKey) - Text(count.formatted()) +// Text(count.formatted()) } } } #Preview { - TournamentFieldsManagerView(localizedStringKey: "test", count: .constant(2)) + TournamentFieldsManagerView(localizedStringKey: "test", count: .constant(2), max: 10) .environment(Tournament.mock()) } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 041f9ec..dd6a40b 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -418,14 +418,16 @@ struct InscriptionManagerView: View { .environment(tournament) } label: { LabeledContent { - Text(count.formatted() + "/" + tournament.teamCount.formatted()) + Text(tournament.registrationIssues().formatted()).font(.largeTitle) } label: { - Text("Analyse des inscriptions") + Text("Problèmes détéctés") if let closedRegistrationDate = tournament.closedRegistrationDate { Text("clôturé le " + closedRegistrationDate.formatted()) } } } + } header: { + Text(count.formatted() + "/" + tournament.teamCount.formatted() + " paires inscrites") } } @@ -739,7 +741,7 @@ struct InscriptionManagerView: View { private func _teamFooterView(_ team: TeamRegistration) -> some View { HStack { if let formattedRegistrationDate = team.formattedInscriptionDate() { - Text(formattedRegistrationDate).foregroundStyle(.secondary) + Text(formattedRegistrationDate) } Spacer() _teamMenuOptionView(team) @@ -748,7 +750,7 @@ struct InscriptionManagerView: View { private func _teamMenuOptionView(_ team: TeamRegistration) -> some View { Menu { Section { - Button("Modifier l'équipe") { + Button("Changer les joueurs") { editedTeam = team team.unsortedPlayers().forEach { player in createdPlayers.insert(player) @@ -756,7 +758,13 @@ struct InscriptionManagerView: View { } } 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 diff --git a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift index 3f10f50..af1e0d3 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift @@ -7,11 +7,18 @@ import SwiftUI -enum CallDestination: String, Identifiable, Selectable { - case seeds - case groupStages +enum CallDestination: Identifiable, Selectable { + case seeds(Tournament) + case groupStages(Tournament) - var id: String { self.rawValue } + var id: String { + switch self { + case .seeds: + return "seed" + case .groupStages: + return "groupStage" + } + } func selectionLabel() -> String { switch self { @@ -23,8 +30,27 @@ enum CallDestination: String, Identifiable, Selectable { } func badgeValue() -> Int? { - nil + switch self { + case .seeds(let tournament): + let allSeedCalled = tournament.seeds().filter({ tournament.isStartDateIsDifferentThanCallDate($0) || $0.callDate == nil }) + return allSeedCalled.count + case .groupStages(let tournament): + let allSeedCalled = tournament.groupStageTeams().filter({ tournament.isStartDateIsDifferentThanCallDate($0) || $0.callDate == nil }) + return allSeedCalled.count + } + } + + func badgeImage() -> String? { + switch self { + case .seeds(let tournament): + let allSeedCalled = tournament.seeds().allSatisfy({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) + return allSeedCalled ? "checkmark.circle.fill" : nil + case .groupStages(let tournament): + let allSeedCalled = tournament.groupStageTeams().allSatisfy({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) + return allSeedCalled ? "checkmark.circle.fill" : nil + } } + } @@ -38,13 +64,13 @@ struct TournamentCallView: View { var destinations = [CallDestination]() let groupStageTeams = tournament.groupStageTeams() if groupStageTeams.isEmpty == false { - destinations.append(.groupStages) - self._selectedDestination = State(wrappedValue: .groupStages) + destinations.append(.groupStages(tournament)) + self._selectedDestination = State(wrappedValue: .groupStages(tournament)) } if tournament.seededTeams().isEmpty == false { - destinations.append(.seeds) + destinations.append(.seeds(tournament)) if groupStageTeams.isEmpty { - self._selectedDestination = State(wrappedValue: .seeds) + self._selectedDestination = State(wrappedValue: .seeds(tournament)) } } self.allDestinations = destinations diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index b6a6445..9f1730d 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -11,7 +11,7 @@ enum CashierDestination: Identifiable, Selectable { case summary case groupStage(GroupStage) case bracket(Round) - case all + case all(Tournament) var id: String { switch self { @@ -38,8 +38,31 @@ enum CashierDestination: Identifiable, Selectable { } func badgeValue() -> Int? { - nil + switch self { + case .summary: + return nil + case .groupStage(let groupStage): + return groupStage.unsortedPlayers().filter({ $0.hasPaid() == false }).count + case .bracket(let round): + return round.seeds().flatMap { $0.unsortedPlayers() }.filter({ $0.hasPaid() == false }).count + case .all(let tournament): + return tournament.selectedPlayers().filter({ $0.hasPaid() == false }).count + } } + + func badgeImage() -> String? { + switch self { + case .summary: + return nil + case .groupStage(let groupStage): + return groupStage.unsortedPlayers().allSatisfy({ $0.hasPaid() }) ? "checkmark" : nil + case .bracket(let round): + return round.seeds().flatMap { $0.unsortedPlayers() }.allSatisfy({ $0.hasPaid() }) ? "checkmark" : nil + case .all(let tournament): + return tournament.selectedPlayers().allSatisfy({ $0.hasPaid() }) ? "checkmark" : nil + } + } + } struct TournamentCashierView: View { @@ -47,7 +70,7 @@ struct TournamentCashierView: View { @State private var selectedDestination: CashierDestination? func allDestinations() -> [CashierDestination] { - var allDestinations : [CashierDestination] = [.summary, .all] + var allDestinations : [CashierDestination] = [] let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } allDestinations.append(contentsOf: destinations) tournament.rounds().forEach { round in @@ -55,6 +78,8 @@ struct TournamentCashierView: View { allDestinations.append(CashierDestination.bracket(round)) } } + allDestinations.append(.all(tournament)) + allDestinations.append(.summary) return allDestinations } @@ -83,7 +108,7 @@ struct TournamentCashierView: View { CashierView(tournament: tournament, teams: groupStage.teams()) case .bracket(let round): CashierView(tournament: tournament, teams: round.seeds()) - case .all: + case .all(let tournament): CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()) } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift index 93b3881..fccb5a2 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift @@ -41,6 +41,11 @@ enum ScheduleDestination: String, Identifiable, Selectable { func badgeValue() -> Int? { nil } + + func badgeImage() -> String? { + nil + } + } struct TournamentScheduleView: View { diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index c6c6500..b05d84b 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -12,15 +12,16 @@ struct TournamentSettingsView: View { @EnvironmentObject var dataStore: DataStore @State private var tournamentName: String = "" - + @FocusState private var textFieldIsFocus: Bool + var body: some View { @Bindable var tournament = tournament Form { LabeledContent { TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $tournament.entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) .keyboardType(.decimalPad) - .fixedSize() .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) } label: { Text("Inscription") } @@ -28,7 +29,7 @@ struct TournamentSettingsView: View { LabeledContent { TextField("Nom", text: $tournamentName) .multilineTextAlignment(.trailing) - .fixedSize() + .frame(maxWidth: .infinity) .keyboardType(.alphabet) .autocorrectionDisabled() .onSubmit { @@ -44,7 +45,7 @@ struct TournamentSettingsView: View { TournamentLevelPickerView() TournamentDurationManagerView() - TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) + TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100) TournamentDatePickerView() @@ -83,7 +84,6 @@ struct TournamentSettingsView: View { event.club = nil try? dataStore.events.addOrUpdate(instance: event) } - .font(.caption) } } } @@ -91,8 +91,17 @@ struct TournamentSettingsView: View { TournamentFormatSelectionView() } + .focused($textFieldIsFocus) + .scrollDismissesKeyboard(.immediately) .navigationTitle("Réglages") .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Valider") { + textFieldIsFocus = false + } + } + } .onDisappear { try? dataStore.tournaments.addOrUpdate(instance: tournament) } diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 3d1194c..2bd172b 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -49,6 +49,7 @@ struct TournamentRunningView: View { NavigationLink(value: Screen.groupStage) { LabeledContent { Text(tournament.groupStageStatus()) + .foregroundStyle(.master) } label: { Text("Poules") } @@ -60,6 +61,7 @@ struct TournamentRunningView: View { NavigationLink(value: Screen.round) { LabeledContent { Text(tournament.bracketStatus()) + .foregroundStyle(.master) } label: { Text("Tableau") } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index c27cfde..c4cfda0 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -34,6 +34,7 @@ struct TournamentView: View { NavigationLink(value: Screen.inscription) { LabeledContent { Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) + .foregroundStyle(.master) } label: { Text("Gestion des inscriptions") if let closedRegistrationDate = tournament.closedRegistrationDate { @@ -44,6 +45,7 @@ struct TournamentView: View { if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false { LabeledContent { Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened)) + .foregroundStyle(.master) } label: { Text("Date limite") } diff --git a/PadelClub/Views/ViewModifiers/DeferredViewModifier.swift b/PadelClub/Views/ViewModifiers/DeferredViewModifier.swift index 98c4591..6a2976d 100644 --- a/PadelClub/Views/ViewModifiers/DeferredViewModifier.swift +++ b/PadelClub/Views/ViewModifiers/DeferredViewModifier.swift @@ -49,6 +49,19 @@ private struct DeferredViewModifier: ViewModifier { } extension View { + func toastFormatted() -> some View { + self + .font(.title3) + .frame(height: 28) + .padding() + .background { + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(.white) + } + .shadow(radius: 2) + .offset(y: -64) + } + func deferredRendering(for delay: DispatchTimeInterval) -> some View { modifier(DeferredViewModifier(delay: delay)) }