From 16c750f95011cd34c5311ba546378cf9afb22b2e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 28 Apr 2024 12:35:22 +0200 Subject: [PATCH] add start date to groupstage --- PadelClub.xcodeproj/project.pbxproj | 4 + PadelClub/Data/PlayerRegistration.swift | 3 + PadelClub/Data/Round.swift | 18 +++++ PadelClub/Data/Tournament.swift | 4 +- PadelClub/ViewModel/SeedInterval.swift | 6 ++ .../Views/Calling/CallSettingsView.swift | 7 +- PadelClub/Views/Calling/CallView.swift | 2 +- .../Views/Calling/GroupStageCallingView.swift | 13 ++++ .../Views/Calling/SeedsCallingView.swift | 13 ++++ PadelClub/Views/Calling/SendToAllView.swift | 74 +++++++++++++++++++ .../Components/DateUpdateManagerView.swift | 8 ++ PadelClub/Views/Round/LoserRoundView.swift | 12 ++- PadelClub/Views/Round/RoundView.swift | 19 ++++- 13 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 PadelClub/Views/Calling/SendToAllView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 0cf439a..9f0af5b 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; }; FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; }; FF2BE4882B85E27400592328 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; @@ -422,6 +423,7 @@ FF1DC5582BAB767000FD8220 /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = ""; }; FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayContext.swift; sourceTree = ""; }; FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonView.swift; sourceTree = ""; }; + FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = ""; }; @@ -1084,6 +1086,7 @@ FF9268022BCE94A30080F940 /* GroupStageCallingView.swift */, FF9268082BCEDC2C0080F940 /* CallView.swift */, FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */, + FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */, ); path = Calling; sourceTree = ""; @@ -1467,6 +1470,7 @@ C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, + FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 5f06bf3..a3db02b 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -305,6 +305,7 @@ class PlayerRegistration: ModelObject, Storable { case bankTransfer = 5 case clubHouse = 6 case creditCard = 7 + case forfeit = 8 func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { @@ -322,6 +323,8 @@ class PlayerRegistration: ModelObject, Storable { return "Clubhouse" case .creditCard: return "CB" + case .forfeit: + return "Forfait" case .gift: return "Offert" } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 10b5b0d..2038e8f 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -122,6 +122,10 @@ class Round: ModelObject, Storable { _matches().compactMap { $0.losingTeamId }.compactMap { Store.main.findById($0) } } + func teams() -> [TeamRegistration] { + playedMatches().flatMap({ $0.teams() }) + } + func roundProjectedTeam(_ team: TeamPosition, inMatch match: Match) -> TeamRegistration? { if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) { return seed @@ -199,6 +203,16 @@ class Round: ModelObject, Storable { Store.main.filter { $0.round == self.id && $0.disabled == false } } + func displayableMatches() -> [Match] { + if index == 0 && isUpperBracket() { + var matches : [Match?] = [playedMatches().first] + matches.append(loserRounds().first?.playedMatches().first) + return matches.compactMap({ $0 }) + } else { + return playedMatches() + } + } + func playedMatches() -> [Match] { if parent == nil { enabledMatches() @@ -370,6 +384,10 @@ class Round: ModelObject, Storable { return loserRounds + loserRounds.flatMap({ $0.loserRoundsAndChildren() }) } + func isUpperBracket() -> Bool { + parent == nil + } + func isLoserBracket() -> Bool { parent != nil } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 3ea6e3b..5506196 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -562,13 +562,13 @@ class Tournament : ModelObject, Storable { } func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool { - guard let callDate = team.callDate else { return false } + guard let callDate = team.callDate else { return true } if let groupStageStartDate = team.groupStageObject()?.startDate { return Calendar.current.compare(callDate, to: groupStageStartDate, toGranularity: .minute) != ComparisonResult.orderedSame } else if let roundMatchStartDate = team.initialMatch()?.startDate { return Calendar.current.compare(callDate, to: roundMatchStartDate, toGranularity: .minute) != ComparisonResult.orderedSame } - return false + return true } func availableToStart(_ allMatches: [Match]) -> [Match] { diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index a4d1af6..93e778e 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -12,6 +12,12 @@ struct SeedInterval: Hashable, Comparable { let last: Int var reduce: Int = 0 + func pointsRange(tournamentLevel: TournamentLevel, teamsCount: Int) -> String { + let range = [tournamentLevel.points(for: last - 1 - reduce, count: teamsCount), + tournamentLevel.points(for: first - 1 - reduce, count: teamsCount)] + return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" + } + static func <(lhs: SeedInterval, rhs: SeedInterval) -> Bool { return lhs.first < rhs.first } diff --git a/PadelClub/Views/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index b718ef6..2811cd9 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -10,6 +10,7 @@ import SwiftUI struct CallSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament + @State private var showSendToAllView: Bool = false var body: some View { List { @@ -24,7 +25,7 @@ struct CallSettingsView: View { Section { RowButtonView("Envoyer un message à tout le monde") { - + showSendToAllView = true } } @@ -48,6 +49,10 @@ struct CallSettingsView: View { } } } + .sheet(isPresented: $showSendToAllView) { + SendToAllView() + .tint(.master) + } } } diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 1126f31..2b85926 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -20,7 +20,7 @@ struct CallView: View { if let startDate { Text(startDate.formattedAsHourMinute()) } else { - Text("Aucun horaire") + Text("Aucun horaire").font(.body) } Spacer() Text(count.formatted() + "/" + total.formatted()) diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 1f8de11..6e74ce4 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -83,6 +83,19 @@ struct GroupStageCallingView: View { } } + .overlay { + if groupStage.startDate == nil { + ContentUnavailableView { + Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") + } description: { + Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") + } actions: { +// RowButtonView("Horaire intelligent") { +// selectedScheduleDestination = nil +// } + } + } + } .headerProminence(.increased) .navigationTitle(groupStage.groupStageTitle()) .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index d3b0400..4ce94e5 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -85,6 +85,19 @@ struct SeedsCallingView: View { } } } + .overlay { + if (keys.isEmpty && displayByMatch == false) || (displayByMatch && roundMatches.isEmpty) { + ContentUnavailableView { + Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") + } description: { + Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") + } actions: { +// RowButtonView("Horaire intelligent") { +// selectedScheduleDestination = nil +// } + } + } + } .headerProminence(.increased) .navigationTitle(round.roundTitle()) .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift new file mode 100644 index 0000000..6dc1b05 --- /dev/null +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -0,0 +1,74 @@ +// +// SendToAllView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 28/04/2024. +// + +import SwiftUI +import LeStorage + +struct SendToAllView: View { + @Environment(Tournament.self) var tournament: Tournament + @State private var contactMethod: Int = 1 + @State private var contactRecipients: Set = Set() + + + var body: some View { + NavigationStack { + List(selection: $contactRecipients) { + Section { + Picker(selection: $contactMethod) { + Text("par sms").tag(0) + Text("par mail").tag(1) + } label: { + Text("méthode") + } + .labelsHidden() + .pickerStyle(.inline) + + } + + Section { + ForEach(tournament.rounds()) { round in + LabeledContent { + let seeds = round.seeds() + Text(round.seeds().count + " tête" + seeds.count.pluralSuffix + " de série") + } label: { + Text(round.roundTitle()) + } + .tag(round.id) + } + } + + Section { + RowButtonView("Contacter \(totalString)") { + + } + } + } + .environment(\.editMode, Binding.constant(EditMode.active)) + .headerProminence(.increased) + .navigationTitle("Réglages") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + } + + func _teams() -> [TeamRegistration] { + let rounds : [Round] = contactRecipients.map { Store.main.findById($0) } + return rounds.flatMap({ $0.teams() }) + } + + func _totalString() -> String { + if contactRecipients.isEmpty { + return "toutes les équipes" + } else { + return + } + } +} + +#Preview { + SendToAllView() +} diff --git a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift index 51e8c29..45253be 100644 --- a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift +++ b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift @@ -41,6 +41,14 @@ struct DatePickingView: View { } else { HStack { Menu { + Button("de 30 minutes") { + startDate = startDate.addingTimeInterval(1800) + } + + Button("d'une heure") { + startDate = startDate.addingTimeInterval(3600) + } + Button("à 9h") { startDate = startDate.atNine() } diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index e050d79..1ed9c82 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -9,6 +9,8 @@ import SwiftUI struct LoserRoundView: View { @EnvironmentObject var dataStore: DataStore + @Environment(Tournament.self) var tournament: Tournament + let loserRounds: [Round] @State private var isEditingTournamentSeed: Bool = false @@ -39,7 +41,15 @@ struct LoserRoundView: View { .disabled(match.disabled) } } header: { - Text(loserRound.roundTitle(.wide)) + HStack { + Text(loserRound.roundTitle(.wide)) + let tournamentTeamCount = tournament.teamCount + if let seedIntervalPointRange = loserRound.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } } } } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 52186b3..f00ea1f 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -17,8 +17,9 @@ struct RoundView: View { var body: some View { List { + let loserRounds = round.loserRounds() + if isEditingTournamentSeed.wrappedValue == false { - let loserRounds = round.loserRounds() //(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue }) if loserRounds.isEmpty == false, let first = loserRounds.first { let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() @@ -61,11 +62,23 @@ struct RoundView: View { } } - ForEach(round.playedMatches()) { match in + ForEach(round.displayableMatches()) { match in Section { MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) } header: { - Text(round.roundTitle(.wide) + " " + match.matchTitle(.short)) + HStack { + Text(round.roundTitle(.wide)) + if round.index > 0 { + Text(match.matchTitle(.short)) + } else { + let tournamentTeamCount = tournament.teamCount + if let seedIntervalPointRange = loserRounds.first?.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } + } } } }