From 8508bedccaada881d5e609a87acedfa6c907d6a8 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 24 Jun 2024 21:37:09 +0200 Subject: [PATCH 01/10] fix seed interval stuff for loser bracket disable account check --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/Round.swift | 16 ++++- PadelClub/Data/Tournament.swift | 31 ++++++++-- PadelClub/ViewModel/SeedInterval.swift | 27 ++++++++ PadelClub/Views/Calling/CallView.swift | 11 ++-- .../Calling/Components/MenuWarningView.swift | 11 ++-- PadelClub/Views/Calling/SendToAllView.swift | 11 ++-- .../Match/Components/MatchDateView.swift | 16 +++++ PadelClub/Views/Match/MatchDetailView.swift | 11 ++-- PadelClub/Views/Round/LoserRoundView.swift | 62 ++++++++++++------- 10 files changed, 151 insertions(+), 49 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index be6ed30..9888ad1 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1912,7 +1912,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 67; + CURRENT_PROJECT_VERSION = 68; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1953,7 +1953,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 67; + CURRENT_PROJECT_VERSION = 68; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index b6e701b..c8d95be 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -363,7 +363,7 @@ class Round: ModelObject, Storable { } func hasNextRound() -> Bool { - return nextRound()?.isDisabled() == false + return nextRound()?.isRankDisabled() == false } func seedInterval() -> SeedInterval? { @@ -395,6 +395,20 @@ class Round: ModelObject, Storable { } return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) } + + func roundTitleAndPointsRange(_ displayStyle: DisplayStyle = .wide, expanded: Bool = false, inTournament: Tournament) -> (String?, String?) { + let seedInterval = seedInterval()?.withLast(enabledMatches().count * 2 - 1) + var roundTitle : String? = nil + if parent != nil { + roundTitle = seedInterval?.localizedLabel(displayStyle) ?? "Round pas trouvé" + } else { + roundTitle = RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) + } + + let tournamentTeamCount = inTournament.teamCount + let pointsEarned: String? = seedInterval?.pointsRange(tournamentLevel: inTournament.tournamentLevel, teamsCount: tournamentTeamCount) + return (roundTitle, pointsEarned) + } func updateTournamentState() { if let tournamentObject = tournamentObject(), index == 0, isUpperBracket(), hasEnded() { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index d2fac20..3841997 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1094,22 +1094,45 @@ class Tournament : ModelObject, Storable { } let others: [Round] = rounds.flatMap { round in - round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false } + print("round", round.roundTitle()) + let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false } + print(rounds.count, rounds.map { $0.roundTitle() }) + return rounds }.compactMap({ $0 }) others.forEach { round in + print("round", round.roundTitle()) if let interval = round.seedInterval() { + print("interval", interval.localizedLabel()) let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() } + print("playedMatches", playedMatches.count) let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false }) + print("winners", winners.count) let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false }) + print("losers", losers.count) if winners.isEmpty { let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) - teams[interval.last] = disabledIds + teams[interval.computedLast] = disabledIds + let teamNames : [String] = disabledIds.compactMap { + let t : TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("winners.isEmpty", "\(interval.computedLast) : ", teamNames) disabledIds.forEach { ids.insert($0) } } else { - teams[interval.first + winners.count - 1] = winners + teams[interval.computedFirst + winners.count - 1] = winners + let teamNames : [String] = winners.compactMap { + let t: TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("winners", "\(interval.computedFirst + winners.count - 1) : ", teamNames) winners.forEach { ids.insert($0) } - teams[interval.last] = losers + teams[interval.computedLast] = losers + let loserTeamNames : [String] = losers.compactMap { + let t: TeamRegistration? = Store.main.findById($0) + return t + }.map { $0.canonicalName } + print("losers", "\(interval.computedLast) : ", loserTeamNames) losers.forEach { ids.insert($0) } } } diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index 93e778e..e44455e 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -26,6 +26,14 @@ struct SeedInterval: Hashable, Comparable { first == 1 && last == 2 } + func reducedBy(_ count: Int, firstAlso: Bool = false) -> SeedInterval { + return SeedInterval(first: first - (firstAlso ? count : 0), last: last - count, reduce: reduce) + } + + func withLast(_ lastValue: Int) -> SeedInterval { + return SeedInterval(first: first, last: first + lastValue, reduce: reduce) + } + var count: Int { dimension } @@ -44,7 +52,26 @@ struct SeedInterval: Hashable, Comparable { return nil } } + + func chunksOrSelf() -> [SeedInterval] { + if dimension > 3 { + let split = dimension / 2 + let firstHalf = SeedInterval(first: first, last: first + split - 1, reduce: reduce) + let secondHalf = SeedInterval(first: first + split, last: last, reduce: reduce) + return [firstHalf, secondHalf] + } else { + return [self] + } + } + var computedLast: Int { + last - reduce + } + + var computedFirst: Int { + first - reduce + } + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { if dimension < 2 { return "#\(first - reduce) / #\(last - reduce)" diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 8df33dc..6aae0fd 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -229,11 +229,12 @@ struct CallView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - if Store.main.userId != nil { - handler() - } else { - self.showUserCreationView = true - } + handler() +// if Store.main.userId != nil { +// handler() +// } else { +// self.showUserCreationView = true +// } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index fb16e2a..0f7376a 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -142,11 +142,12 @@ struct MenuWarningView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - if Store.main.userId != nil { - handler() - } else { - self.showUserCreationView = true - } + handler() +// if Store.main.userId != nil { +// handler() +// } else { +// self.showUserCreationView = true +// } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index f35a6bb..2ee2938 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -229,11 +229,12 @@ struct SendToAllView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - if Store.main.userId != nil { - handler() - } else { - self.showUserCreationView = true - } + handler() +// if Store.main.userId != nil { +// handler() +// } else { +// self.showUserCreationView = true +// } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index 653c5b6..9324f15 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -26,6 +26,22 @@ struct MatchDateView: View { var body: some View { Menu { + Button("Ne pas jouer ce match") { + match._toggleMatchDisableState(true) + } + Button("Jouer ce match") { + match._toggleMatchDisableState(false) + } + + Button("Créer les scores") { + let teamsScores = match.getOrCreateTeamScores() + do { + try dataStore.teamScores.addOrUpdate(contentOfs: teamsScores) + } catch { + Logger.error(error) + } + } + let estimatedDuration = match.getDuration() if match.startDate == nil && isReady { Button("Démarrer") { diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 1107f1b..91a4b7a 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -442,11 +442,12 @@ struct MatchDetailView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - if Store.main.userId != nil { - handler() - } else { - self.showUserCreationView = true - } + handler() +// if Store.main.userId != nil { +// handler() +// } else { +// self.showUserCreationView = true +// } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index ceb9efa..d58dc52 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -23,35 +23,53 @@ struct LoserRoundView: View { if isEditingTournamentSeed == true { _editingView() } + let shouldDisplayLoserRounds = loserRounds.filter({ + let matches = $0.playedMatches().filter { isEditingTournamentSeed == true || (isEditingTournamentSeed == false && $0.disabled == false) } + return matches.isEmpty == false + }).isEmpty == false - ForEach(loserRounds) { loserRound in - if true { - Section { - let matches = loserRound.playedMatches().sorted(by: \.index) - ForEach(matches) { match in - MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) - .overlay { - if match.disabled /*&& isEditingTournamentSeed*/ { - Image(systemName: "xmark") - .resizable() - .scaledToFit() - .opacity(0.8) + if shouldDisplayLoserRounds { + ForEach(loserRounds) { loserRound in + let matches = loserRound.playedMatches().filter { isEditingTournamentSeed == true || (isEditingTournamentSeed == false && $0.disabled == false) }.sorted(by: \.index) + if matches.isEmpty == false { + Section { + ForEach(matches) { match in + MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + .overlay { + if match.disabled && isEditingTournamentSeed { + Image(systemName: "xmark") + .resizable() + .scaledToFit() + .opacity(0.8) + } + } + .disabled(match.disabled) + + if isEditingTournamentSeed { + RowButtonView(match.disabled ? "Jouer ce match" : "Ne pas jouer ce match", role: .destructive) { + match._toggleMatchDisableState(!match.disabled) } } - .disabled(match.disabled) - } - } header: { - 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) + } + } header: { + HStack { + let labels = loserRound.roundTitleAndPointsRange(.wide, expanded: isEditingTournamentSeed, inTournament: tournament) + if let seedIntervalLocalizedLabel = labels.0 { + Text(seedIntervalLocalizedLabel) + } + if let seedIntervalPointRange = labels.1 { + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } } } } } + } else { + Section { + ContentUnavailableView("Aucun match joué", systemImage: "tennisball", description: Text("Il n'y aucun match à jouer dans ce tour de match de classement.")) + } } } .headerProminence(.increased) From b97a533a1054bf087772bf324ed29cfcb8853e8a Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 25 Jun 2024 19:56:07 +0200 Subject: [PATCH 02/10] performance boost --- PadelClub/Data/Club.swift | 8 +- PadelClub/Data/Event.swift | 4 +- PadelClub/Data/GroupStage.swift | 16 +- PadelClub/Data/Match.swift | 96 ++++---- PadelClub/Data/MonthData.swift | 2 +- PadelClub/Data/Round.swift | 226 ++++++++++++------ PadelClub/Data/TeamRegistration.swift | 16 +- PadelClub/Data/Tournament.swift | 67 ++++-- PadelClub/Data/User.swift | 4 +- PadelClub/ViewModel/AgendaDestination.swift | 2 +- .../ViewModel/FederalDataViewModel.swift | 4 +- PadelClub/ViewModel/SeedInterval.swift | 21 +- PadelClub/ViewModel/Selectable.swift | 2 +- .../Cashier/Event/EventCreationView.swift | 21 +- PadelClub/Views/Cashier/Event/EventView.swift | 2 +- .../GenericDestinationPickerView.swift | 5 +- .../Views/GroupStage/GroupStagesView.swift | 2 +- .../Match/Components/MatchDateView.swift | 16 -- .../Match/Components/PlayerBlockView.swift | 11 +- PadelClub/Views/Match/MatchDetailView.swift | 27 ++- PadelClub/Views/Match/MatchRowView.swift | 5 +- PadelClub/Views/Match/MatchSummaryView.swift | 10 +- PadelClub/Views/Round/LoserRoundView.swift | 98 ++++---- PadelClub/Views/Round/LoserRoundsView.swift | 146 +++++++++-- PadelClub/Views/Round/RoundView.swift | 69 ++++-- PadelClub/Views/Round/RoundsView.swift | 14 +- .../Screen/InscriptionManagerView.swift | 8 +- .../Screen/TournamentCallView.swift | 2 +- .../Screen/TournamentCashierView.swift | 6 +- .../Screen/TournamentRankView.swift | 26 +- .../Screen/TournamentScheduleView.swift | 2 +- .../Screen/TournamentSettingsView.swift | 2 +- .../Views/Tournament/TournamentView.swift | 4 +- PadelClub/Views/User/LoginView.swift | 2 +- 34 files changed, 606 insertions(+), 340 deletions(-) diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index 53a3661..f18437c 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -75,7 +75,7 @@ class Club : ModelObject, Storable, Hashable { } var customizedCourts: [Court] { - Store.main.filter { $0.club == self.id }.sorted(by: \.index) + DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index) } override func deleteDependencies() throws { @@ -230,10 +230,10 @@ extension Club { identify a club : code, name, ?? */ - let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) + let club: Club? = DataStore.shared.clubs.first(where: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) - if clubs.isEmpty == false { - return clubs.first! + if let club { + return club } else { return Club(creator: Store.main.userId, name: name, code: code, city: city, zipCode: zipCode) } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index bf73fcb..679bdad 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -37,7 +37,7 @@ class Event: ModelObject, Storable { // MARK: - Computed dependencies var tournaments: [Tournament] { - Store.main.filter { $0.event == self.id } + DataStore.shared.tournaments.filter { $0.event == self.id } } func clubObject() -> Club? { @@ -46,7 +46,7 @@ class Event: ModelObject, Storable { } var courtsUnavailability: [DateInterval] { - Store.main.filter(isIncluded: { $0.event == id }) + DataStore.shared.dateIntervals.filter({ $0.event == id }) } // MARK: - diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 7fa5218..af6175a 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -44,7 +44,7 @@ class GroupStage: ModelObject, Storable { // MARK: - Computed dependencies func _matches() -> [Match] { - Store.main.filter { $0.groupStage == self.id } + DataStore.shared.matches.filter { $0.groupStage == self.id } } func tournamentObject() -> Tournament? { @@ -184,7 +184,7 @@ class GroupStage: ModelObject, Storable { } func availableToStart(playedMatches: [Match], in runningMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -195,7 +195,7 @@ class GroupStage: ModelObject, Storable { } func runningMatches(playedMatches: [Match]) -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -206,7 +206,7 @@ class GroupStage: ModelObject, Storable { } func asyncRunningMatches(playedMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -218,7 +218,7 @@ class GroupStage: ModelObject, Storable { func readyMatches(playedMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -229,7 +229,7 @@ class GroupStage: ModelObject, Storable { } func finishedMatches(playedMatches: [Match]) -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -315,7 +315,7 @@ class GroupStage: ModelObject, Storable { } func unsortedTeams() -> [TeamRegistration] { - Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } + DataStore.shared.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } } func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { @@ -416,7 +416,7 @@ extension GroupStage { } extension GroupStage: Selectable { - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { groupStageTitle() } diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 7d94af6..26814b2 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -60,7 +60,7 @@ class Match: ModelObject, Storable { // MARK: - Computed dependencies var teamScores: [TeamScore] { - return Store.main.filter { $0.match == self.id } + return DataStore.shared.teamScores.filter { $0.match == self.id } } // MARK: - @@ -70,6 +70,13 @@ class Match: ModelObject, Storable { } func indexInRound(in matches: [Match]? = nil) -> Int { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func indexInRound(in", matches?.count, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif if groupStage != nil { return index } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) { @@ -87,6 +94,13 @@ class Match: ModelObject, Storable { } func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func matchTitle", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif if let groupStageObject { return groupStageObject.localizedMatchUpLabel(for: index) } @@ -285,13 +299,13 @@ class Match: ModelObject, Storable { let bottomMatchIndex = (nextIndex + 1) * 2 let isTopMatch = topMatchIndex + 1 == index let lookingForIndex = isTopMatch ? topMatchIndex : bottomMatchIndex - return Store.main.filter(isIncluded: { $0.round == round && $0.index == lookingForIndex }).first + return DataStore.shared.matches.first(where: { $0.round == round && $0.index == lookingForIndex }) } private func _forwardMatch(inRound round: Round) -> Match? { guard let roundObjectNextRound = round.nextRound() else { return nil } let nextIndex = (index - 1) / 2 - return Store.main.filter(isIncluded: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }).first + return DataStore.shared.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }) } func _toggleForwardMatchDisableState(_ state: Bool) { @@ -350,7 +364,7 @@ class Match: ModelObject, Storable { } func next() -> Match? { - return Store.main.filter(isIncluded: { $0.round == round && $0.index > index }).sorted(by: \.index).first + return DataStore.shared.matches.filter({ $0.round == round && $0.index > index }).sorted(by: \.index).first } func followingMatch() -> Match? { @@ -359,7 +373,7 @@ class Match: ModelObject, Storable { } func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? { - return Store.main.filter(isIncluded: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }).first + return DataStore.shared.matches.first(where: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }) } func getDuration() -> Int { @@ -386,26 +400,20 @@ class Match: ModelObject, Storable { func topPreviousRoundMatch() -> Match? { guard let roundObject else { return nil } - let matches: [Match] = Store.main.filter { match in - match.index == topPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id - } - return matches.sorted(by: \.index).first + let topPreviousRoundMatchIndex = topPreviousRoundMatchIndex() + let roundObjectPreviousRoundId = roundObject.previousRound()?.id + return DataStore.shared.matches.first(where: { match in + match.round != nil && match.round == roundObjectPreviousRoundId && match.index == topPreviousRoundMatchIndex + }) } func bottomPreviousRoundMatch() -> Match? { guard let roundObject else { return nil } - let matches: [Match] = Store.main.filter { match in - match.index == bottomPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id - } - return matches.sorted(by: \.index).first - } - - func upperBracketMatch(_ teamPosition: TeamPosition) -> Match? { - if teamPosition == .one { - return roundObject?.upperBracketTopMatch(ofMatchIndex: index) - } else { - return roundObject?.upperBracketBottomMatch(ofMatchIndex: index) - } + let bottomPreviousRoundMatchIndex = bottomPreviousRoundMatchIndex() + let roundObjectPreviousRoundId = roundObject.previousRound()?.id + return DataStore.shared.matches.first(where: { match in + match.round != nil && match.round == roundObjectPreviousRoundId && match.index == bottomPreviousRoundMatchIndex + }) } func previousMatch(_ teamPosition: TeamPosition) -> Match? { @@ -416,11 +424,6 @@ class Match: ModelObject, Storable { } } - func upperMatches() -> [Match] { - guard let roundObject else { return [] } - return [roundObject.upperBracketTopMatch(ofMatchIndex: index), roundObject.upperBracketBottomMatch(ofMatchIndex: index)].compactMap({ $0 }) - } - var computedOrder: Int { guard let roundObject else { return index } return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound() @@ -428,9 +431,9 @@ class Match: ModelObject, Storable { func previousMatches() -> [Match] { guard let roundObject else { return [] } - return Store.main.filter { match in - (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) - && match.round == roundObject.previousRound()?.id + let roundObjectPreviousRoundId = roundObject.previousRound()?.id + return DataStore.shared.matches.filter { match in + match.round == roundObjectPreviousRoundId && (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) }.sorted(by: \.index) } @@ -660,14 +663,26 @@ class Match: ModelObject, Storable { } func scores() -> [TeamScore] { - return Store.main.filter(isIncluded: { $0.match == id }) + return DataStore.shared.teamScores.filter { $0.match == id } } func teams() -> [TeamRegistration] { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func teams()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + if groupStage != nil { return [groupStageProjectedTeam(.one), groupStageProjectedTeam(.two)].compactMap { $0 } } - return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 } + guard let roundObject else { return [] } + let previousRound = roundObject.previousRound() + return [roundObject.roundProjectedTeam(.one, inMatch: self, previousRound: previousRound), roundObject.roundProjectedTeam(.two, inMatch: self, previousRound: previousRound)].compactMap { $0 } + +// return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 } } func scoreDifference(_ teamPosition: Int) -> (set: Int, game: Int)? { @@ -696,7 +711,8 @@ class Match: ModelObject, Storable { func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? { guard let roundObject else { return nil } - return roundObject.roundProjectedTeam(team, inMatch: self) + let previousRound = roundObject.previousRound() + return roundObject.roundProjectedTeam(team, inMatch: self, previousRound: previousRound) } func teamWon(_ team: TeamRegistration?) -> Bool { @@ -710,7 +726,7 @@ class Match: ModelObject, Storable { } func team(_ team: TeamPosition) -> TeamRegistration? { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -718,19 +734,9 @@ class Match: ModelObject, Storable { } #endif if groupStage != nil { - switch team { - case .one: - return groupStageProjectedTeam(.one) - case .two: - return groupStageProjectedTeam(.two) - } + return groupStageProjectedTeam(team) } else { - switch team { - case .one: - return roundProjectedTeam(.one) - case .two: - return roundProjectedTeam(.two) - } + return roundProjectedTeam(team) } } diff --git a/PadelClub/Data/MonthData.swift b/PadelClub/Data/MonthData.swift index 7580fb1..8ad05c8 100644 --- a/PadelClub/Data/MonthData.swift +++ b/PadelClub/Data/MonthData.swift @@ -41,7 +41,7 @@ class MonthData : ModelObject, Storable { let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: mostRecentDateAvailable) await MainActor.run { if let lastDataSource = DataStore.shared.appSettings.lastDataSource { - let currentMonthData : MonthData = Store.main.filter(isIncluded: { $0.monthKey == lastDataSource }).first ?? MonthData(monthKey: lastDataSource) + let currentMonthData : MonthData = DataStore.shared.monthData.first(where: { $0.monthKey == lastDataSource }) ?? MonthData(monthKey: lastDataSource) currentMonthData.maleUnrankedValue = lastDataSourceMaleUnranked?.0 currentMonthData.maleCount = lastDataSourceMaleUnranked?.1 currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0 diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index c8d95be..881f649 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -36,11 +36,11 @@ class Round: ModelObject, Storable { } func _matches() -> [Match] { - return Store.main.filter { $0.round == self.id } + return DataStore.shared.matches.filter { $0.round == self.id } } func getDisabledMatches() -> [Match] { - return Store.main.filter { $0.round == self.id && $0.disabled == true } + return DataStore.shared.matches.filter { $0.round == self.id && $0.disabled == true } } // MARK: - @@ -60,7 +60,11 @@ class Round: ModelObject, Storable { } func hasEnded() -> Bool { - return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false + if parent == nil { + return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false + } else { + return enabledMatches().anySatisfy({ $0.hasEnded() == false }) == false + } } func upperMatches(ofMatch match: Match) -> [Match] { @@ -74,8 +78,8 @@ class Round: ModelObject, Storable { func previousMatches(ofMatch match: Match) -> [Match] { guard let previousRound = previousRound() else { return [] } - return Store.main.filter { - ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) && $0.round == previousRound.id + return DataStore.shared.matches.filter { + $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) } } @@ -92,26 +96,21 @@ class Round: ModelObject, Storable { } } - func team(_ team: TeamPosition, inMatch match: Match) -> TeamRegistration? { - switch team { - case .one: - return roundProjectedTeam(.one, inMatch: match) - case .two: - return roundProjectedTeam(.two, inMatch: match) - } + func team(_ team: TeamPosition, inMatch match: Match, previousRound: Round?) -> TeamRegistration? { + return roundProjectedTeam(team, inMatch: match, previousRound: previousRound) } func seed(_ team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { - return Store.main.filter(isIncluded: { + return DataStore.shared.teamRegistrations.first(where: { $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) == matchIndex && ($0.bracketPosition! % 2) == team.rawValue - }).first + }) } func seeds(inMatchIndex matchIndex: Int) -> [TeamRegistration] { - return Store.main.filter(isIncluded: { + return DataStore.shared.teamRegistrations.filter({ $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) == matchIndex @@ -121,7 +120,7 @@ class Round: ModelObject, Storable { func seeds() -> [TeamRegistration] { let initialMatchIndex = RoundRule.matchIndex(fromRoundIndex: index) let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index) - return Store.main.filter(isIncluded: { + return DataStore.shared.teamRegistrations.filter({ $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) >= initialMatchIndex @@ -137,7 +136,14 @@ class Round: ModelObject, Storable { return playedMatches().flatMap({ $0.teams() }) } - func roundProjectedTeam(_ team: TeamPosition, inMatch match: Match) -> TeamRegistration? { + func roundProjectedTeam(_ team: TeamPosition, inMatch match: Match, previousRound: Round?) -> TeamRegistration? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func roundProjectedTeam", team.rawValue, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) { return seed } @@ -146,76 +152,115 @@ class Round: ModelObject, Storable { case .one: if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 }) { return luckyLoser.team - } else if let parent = upperBracketTopMatch(ofMatchIndex: match.index)?.losingTeamId { - return Store.main.findById(parent) - } else if let previousMatch = topPreviousRoundMatch(ofMatch: match) { + } else if let previousMatch = topPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return Store.main.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } + } else if let parent = upperBracketTopMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { + return Store.main.findById(parent) } case .two: if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) { return luckyLoser.team - } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index)?.losingTeamId { - return Store.main.findById(parent) - } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match) { + } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return Store.main.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } + } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { + return Store.main.findById(parent) } } return nil } - func upperBracketTopMatch(ofMatchIndex matchIndex: Int) -> Match? { + func upperBracketTopMatch(ofMatchIndex matchIndex: Int, previousRound: Round?) -> Match? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func upperBracketTopMatch", matchIndex, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) - if isLoserBracket(), previousRound() == nil, let parentRound = parentRound, let upperBracketTopMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2) { + if isLoserBracket(), previousRound == nil, let parentRound = parentRound, let upperBracketTopMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2) { return upperBracketTopMatch } return nil } - func upperBracketBottomMatch(ofMatchIndex matchIndex: Int) -> Match? { + func upperBracketBottomMatch(ofMatchIndex matchIndex: Int, previousRound: Round?) -> Match? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func upperBracketBottomMatch", matchIndex, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) - if isLoserBracket(), previousRound() == nil, let parentRound = parentRound, let upperBracketBottomMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) { + if isLoserBracket(), previousRound == nil, let parentRound = parentRound, let upperBracketBottomMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) { return upperBracketBottomMatch } return nil } - func topPreviousRoundMatch(ofMatch match: Match) -> Match? { - guard let previousRound = previousRound() else { return nil } - let matches: [Match] = Store.main.filter { - $0.index == match.topPreviousRoundMatchIndex() && $0.round == previousRound.id - } - return matches.sorted(by: \.index).first + func topPreviousRoundMatch(ofMatch match: Match, previousRound: Round?) -> Match? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func topPreviousRoundMatch", match.id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + + guard let previousRound else { return nil } + let topPreviousRoundMatchIndex = match.topPreviousRoundMatchIndex() + return DataStore.shared.matches.first(where: { + $0.round == previousRound.id && $0.index == topPreviousRoundMatchIndex + }) } - func bottomPreviousRoundMatch(ofMatch match: Match) -> Match? { - guard let previousRound = previousRound() else { return nil } - let matches: [Match] = Store.main.filter { - $0.index == match.bottomPreviousRoundMatchIndex() && $0.round == previousRound.id - } - return matches.sorted(by: \.index).first + func bottomPreviousRoundMatch(ofMatch match: Match, previousRound: Round?) -> Match? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func bottomPreviousRoundMatch", match.id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + + guard let previousRound else { return nil } + let bottomPreviousRoundMatchIndex = match.bottomPreviousRoundMatchIndex() + return DataStore.shared.matches.first(where: { + $0.round == previousRound.id && $0.index == bottomPreviousRoundMatchIndex + }) } func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? { - Store.main.filter(isIncluded: { + DataStore.shared.matches.first(where: { let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) return $0.round == id && index == matchIndexInRound - }).first + }) } func enabledMatches() -> [Match] { - return Store.main.filter { $0.round == self.id && $0.disabled == false } + return DataStore.shared.matches.filter { $0.round == self.id && $0.disabled == false } } func displayableMatches() -> [Match] { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func displayableMatches of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + if index == 0 && isUpperBracket() { var matches : [Match?] = [playedMatches().first] matches.append(loserRounds().first?.playedMatches().first) @@ -234,17 +279,28 @@ class Round: ModelObject, Storable { } func previousRound() -> Round? { - return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.parent == parent && $0.index == index + 1 }).first +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func previousRound of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return DataStore.shared.rounds.first(where: { $0.tournament == tournament && $0.parent == parent && $0.index == index + 1 }) } func nextRound() -> Round? { - return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.parent == parent && $0.index == index - 1 }).first + return DataStore.shared.rounds.first(where: { $0.tournament == tournament && $0.parent == parent && $0.index == index - 1 }) } func loserRounds(forRoundIndex roundIndex: Int) -> [Round] { return loserRoundsAndChildren().filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount) } - + + func loserRounds(forRoundIndex roundIndex: Int, loserRoundsAndChildren: [Round]) -> [Round] { + return loserRoundsAndChildren.filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount) + } + func isDisabled() -> Bool { return _matches().allSatisfy({ $0.disabled }) } @@ -351,8 +407,15 @@ class Round: ModelObject, Storable { func correspondingLoserRoundTitle(_ displayStyle: DisplayStyle = .wide) -> String { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func correspondingLoserRoundTitle()", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) - let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: { + let seedsAfterThisRound : [TeamRegistration] = DataStore.shared.teamRegistrations.filter({ $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex @@ -366,23 +429,38 @@ class Round: ModelObject, Storable { return nextRound()?.isRankDisabled() == false } - func seedInterval() -> SeedInterval? { + func seedInterval(expanded: Bool = false) -> SeedInterval? { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func seedInterval(expanded: Bool = false)", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + if parent == nil { - let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index + 1) + if index == 0 { return SeedInterval(first: 1, last: 2) } let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) - let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: { + let seedsAfterThisRound : [TeamRegistration] = DataStore.shared.teamRegistrations.filter({ $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex }) let playedMatches = playedMatches() - let reduce = numberOfMatches / 2 - (playedMatches.count + seedsAfterThisRound.count) - return SeedInterval(first: 1, last: numberOfMatches, reduce: reduce) + let seedInterval = SeedInterval(first: playedMatches.count + seedsAfterThisRound.count + 1, last: playedMatches.count * 2 + seedsAfterThisRound.count) + return seedInterval } if let previousRound = previousRound() { - return previousRound.seedInterval()?.chunks()?.first + if previousRound.enabledMatches().isEmpty == false && expanded == false { + return previousRound.seedInterval()?.chunks()?.first + } else { + return previousRound.previousRound()?.seedInterval() + } } else if let parentRound { + if parentRound.parent == nil && expanded == false { + return parentRound.seedInterval() + } return parentRound.seedInterval()?.chunks()?.last } @@ -395,20 +473,6 @@ class Round: ModelObject, Storable { } return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) } - - func roundTitleAndPointsRange(_ displayStyle: DisplayStyle = .wide, expanded: Bool = false, inTournament: Tournament) -> (String?, String?) { - let seedInterval = seedInterval()?.withLast(enabledMatches().count * 2 - 1) - var roundTitle : String? = nil - if parent != nil { - roundTitle = seedInterval?.localizedLabel(displayStyle) ?? "Round pas trouvé" - } else { - roundTitle = RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) - } - - let tournamentTeamCount = inTournament.teamCount - let pointsEarned: String? = seedInterval?.pointsRange(tournamentLevel: inTournament.tournamentLevel, teamsCount: tournamentTeamCount) - return (roundTitle, pointsEarned) - } func updateTournamentState() { if let tournamentObject = tournamentObject(), index == 0, isUpperBracket(), hasEnded() { @@ -433,7 +497,15 @@ class Round: ModelObject, Storable { } func loserRounds() -> [Round] { - return Store.main.filter(isIncluded: { $0.parent == id }).sorted(by: \.index).reversed() +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func loserRounds: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + return DataStore.shared.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed() } func loserRoundsAndChildren() -> [Round] { @@ -574,7 +646,7 @@ extension Round: Selectable, Equatable { } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { if let parentRound { return "Tour #\(parentRound.loserRounds().count - index)" } else { @@ -583,6 +655,15 @@ extension Round: Selectable, Equatable { } func badgeValue() -> Int? { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeValue round of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + if let parentRound { return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count } else { @@ -595,6 +676,13 @@ extension Round: Selectable, Equatable { } func badgeImage() -> Badge? { - hasEnded() ? .checkmark : nil +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeImage of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return hasEnded() ? .checkmark : nil } } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 824870d..d6df314 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -60,7 +60,7 @@ class TeamRegistration: ModelObject, Storable { // MARK: - Computed dependencies func unsortedPlayers() -> [PlayerRegistration] { - Store.main.filter { $0.teamRegistration == self.id } + DataStore.shared.playerRegistrations.filter { $0.teamRegistration == self.id } } // MARK: - @@ -138,19 +138,19 @@ class TeamRegistration: ModelObject, Storable { } func teamScores() -> [TeamScore] { - return Store.main.filter(isIncluded: { $0.teamRegistration == id }) + return DataStore.shared.teamScores.filter({ $0.teamRegistration == id }) } func wins() -> [Match] { - return Store.main.filter(isIncluded: { $0.winningTeamId == id }) + return DataStore.shared.matches.filter({ $0.winningTeamId == id }) } func loses() -> [Match] { - return Store.main.filter(isIncluded: { $0.losingTeamId == id }) + return DataStore.shared.matches.filter({ $0.losingTeamId == id }) } func matches() -> [Match] { - return Store.main.filter(isIncluded: { $0.losingTeamId == id || $0.winningTeamId == id }) + return DataStore.shared.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id }) } var tournamentCategory: TournamentCategory { @@ -323,7 +323,7 @@ class TeamRegistration: ModelObject, Storable { typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool func players() -> [PlayerRegistration] { - Store.main.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in + DataStore.shared.playerRegistrations.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in let predicates: [AreInIncreasingOrder] = [ { $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 }, { $0.rank ?? 0 < $1.rank ?? 0 }, @@ -377,13 +377,13 @@ class TeamRegistration: ModelObject, Storable { func initialRound() -> Round? { guard let bracketPosition else { return nil } let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) - return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == roundIndex }).first + return DataStore.shared.rounds.first(where: { $0.tournament == tournament && $0.index == roundIndex }) } func initialMatch() -> Match? { guard let bracketPosition else { return nil } guard let initialRoundObject = initialRound() else { return nil } - return Store.main.filter(isIncluded: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }).first + return DataStore.shared.matches.first(where: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }) } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 3841997..5a0ea1d 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -344,7 +344,7 @@ class Tournament : ModelObject, Storable { // MARK: - Computed Dependencies func unsortedTeams() -> [TeamRegistration] { - return Store.main.filter { $0.tournament == self.id } + return DataStore.shared.teamRegistrations.filter { $0.tournament == self.id } } func groupStages() -> [GroupStage] { @@ -353,7 +353,7 @@ class Tournament : ModelObject, Storable { } func allRounds() -> [Round] { - return Store.main.filter { $0.tournament == self.id } + return DataStore.shared.rounds.filter { $0.tournament == self.id } } // MARK: - @@ -476,7 +476,15 @@ class Tournament : ModelObject, Storable { } func courtUsed() -> [Int] { - let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id }) +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func courtUsed()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + + let runningMatches : [Match] = DataStore.shared.matches.filter({ $0.isRunning() }).filter({ $0.tournamentId() == self.id }) return Set(runningMatches.compactMap { $0.courtIndex }).sorted() } @@ -557,6 +565,15 @@ class Tournament : ModelObject, Storable { } func availableSeeds() -> [TeamRegistration] { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func availableSeeds()", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + + return seeds().filter { $0.isSeedable() } } @@ -569,7 +586,7 @@ class Tournament : ModelObject, Storable { } func getRound(atRoundIndex roundIndex: Int) -> Round? { - return Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first + return DataStore.shared.rounds.first(where: { $0.tournament == id && $0.index == roundIndex }) } func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] { @@ -746,18 +763,18 @@ class Tournament : ModelObject, Storable { } func allMatches() -> [Match] { - let unsortedGroupStages : [GroupStage] = Store.main.filter { $0.tournament == self.id } + let unsortedGroupStages : [GroupStage] = DataStore.shared.groupStages.filter { $0.tournament == self.id } let matches: [Match] = unsortedGroupStages.flatMap { $0._matches() } + allRoundMatches() return matches.filter({ $0.disabled == false }) } func _allMatchesIncludingDisabled() -> [Match] { - let unsortedGroupStages : [GroupStage] = Store.main.filter { $0.tournament == self.id } + let unsortedGroupStages : [GroupStage] = DataStore.shared.groupStages.filter { $0.tournament == self.id } return unsortedGroupStages.flatMap { $0._matches() } + allRounds().flatMap { $0._matches() } } func rounds() -> [Round] { - Store.main.filter { $0.tournament == self.id && $0.parent == nil }.sorted(by: \.index).reversed() + DataStore.shared.rounds.filter { $0.tournament == self.id && $0.parent == nil }.sorted(by: \.index).reversed() } func sortedTeams() -> [TeamRegistration] { @@ -766,7 +783,7 @@ class Tournament : ModelObject, Storable { } func selectedSortedTeams() -> [TeamRegistration] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -842,11 +859,11 @@ class Tournament : ModelObject, Storable { } func unsortedTeamsWithoutWO() -> [TeamRegistration] { - return Store.main.filter { $0.tournament == self.id && $0.walkOut == false } + return DataStore.shared.teamRegistrations.filter { $0.tournament == self.id && $0.walkOut == false } } func walkoutTeams() -> [TeamRegistration] { - return Store.main.filter { $0.tournament == self.id && $0.walkOut == true } + return DataStore.shared.teamRegistrations.filter { $0.tournament == self.id && $0.walkOut == true } } func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] { @@ -1015,11 +1032,11 @@ class Tournament : ModelObject, Storable { func groupStagesMatches() -> [Match] { let groupStageIds = groupStages().map { $0.id } - return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) + return DataStore.shared.matches.filter({ $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) } func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -1030,7 +1047,7 @@ class Tournament : ModelObject, Storable { } func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -1041,7 +1058,7 @@ class Tournament : ModelObject, Storable { } func runningMatches(_ allMatches: [Match]) -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -1052,7 +1069,7 @@ class Tournament : ModelObject, Storable { } func readyMatches(_ allMatches: [Match]) async -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -1063,7 +1080,7 @@ class Tournament : ModelObject, Storable { } func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -1268,6 +1285,14 @@ class Tournament : ModelObject, Storable { func availableQualifiedTeams() -> [TeamRegistration] { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func availableQualifiedTeams()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil }) } @@ -1494,8 +1519,8 @@ class Tournament : ModelObject, Storable { guard let bracketPosition else { return nil } let matchIndex = bracketPosition / 2 let roundIndex = RoundRule.roundIndex(fromMatchIndex: matchIndex) - if let round : Round = Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first { - return Store.main.filter(isIncluded: { $0.round == round.id && $0.index == matchIndex }).first + if let round : Round = DataStore.shared.rounds.first(where: { $0.tournament == id && $0.index == roundIndex }) { + return DataStore.shared.matches.first(where: { $0.round == round.id && $0.index == matchIndex }) } return nil } @@ -1759,7 +1784,7 @@ class Tournament : ModelObject, Storable { func currentMonthData() -> MonthData? { guard let rankSourceDate else { return nil } let dateString = URL.importDateFormatter.string(from: rankSourceDate) - return Store.main.filter(isIncluded: { $0.monthKey == dateString }).first + return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) } var maleUnrankedValue: Int? { @@ -1779,9 +1804,9 @@ class Tournament : ModelObject, Storable { } func tournamentWinner() -> TeamRegistration? { - let rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil }) + let round: Round? = DataStore.shared.rounds.first(where: { $0.tournament == id && $0.parent == nil && $0.index == 0 }) // let final: Round? = .first - return rounds.first?.playedMatches().first?.winner() + return round?.playedMatches().first?.winner() } func getGroupStageChunkValue() -> Int { diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index ae24386..4087995 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -86,11 +86,11 @@ class User: ModelObject, UserBase, Storable { } func clubsObjects(includeCreated: Bool = false) -> [Club] { - return Store.main.filter(isIncluded: { (includeCreated && $0.creator == id) || clubs.contains($0.id) }) + return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) } func createdClubsObjectsNotFavorite() -> [Club] { - return Store.main.filter(isIncluded: { ($0.creator == id) && clubs.contains($0.id) == false }) + return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) } func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 7b9c77d..3708e3d 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -35,7 +35,7 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { } } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { localizedTitleKey } diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index 5d71add..387dc0a 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -25,7 +25,7 @@ class FederalDataViewModel { labels.append(contentsOf: categories.map { $0.localizedLabel() }) labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) let clubNames = selectedClubs.compactMap { codeClub in - let club: Club? = Store.main.filter(isIncluded: { $0.code == codeClub }).first + let club: Club? = DataStore.shared.clubs.first(where: { $0.code == codeClub }) return club?.clubTitle(.short) } @@ -35,7 +35,7 @@ class FederalDataViewModel { func selectedClub() -> Club? { if selectedClubs.isEmpty == false { - return Store.main.filter(isIncluded: { $0.code == selectedClubs.first! }).first + return DataStore.shared.clubs.first(where: { $0.code == selectedClubs.first! }) } else { return nil } diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index e44455e..91adb31 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -25,15 +25,7 @@ struct SeedInterval: Hashable, Comparable { func isFixed() -> Bool { first == 1 && last == 2 } - - func reducedBy(_ count: Int, firstAlso: Bool = false) -> SeedInterval { - return SeedInterval(first: first - (firstAlso ? count : 0), last: last - count, reduce: reduce) - } - - func withLast(_ lastValue: Int) -> SeedInterval { - return SeedInterval(first: first, last: first + lastValue, reduce: reduce) - } - + var count: Int { dimension } @@ -53,17 +45,6 @@ struct SeedInterval: Hashable, Comparable { } } - func chunksOrSelf() -> [SeedInterval] { - if dimension > 3 { - let split = dimension / 2 - let firstHalf = SeedInterval(first: first, last: first + split - 1, reduce: reduce) - let secondHalf = SeedInterval(first: first + split, last: last, reduce: reduce) - return [firstHalf, secondHalf] - } else { - return [self] - } - } - var computedLast: Int { last - reduce } diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 0953678..88658e8 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI protocol Selectable { - func selectionLabel() -> String + func selectionLabel(index: Int) -> String func badgeValue() -> Int? func badgeImage() -> Badge? func badgeValueColor() -> Color? diff --git a/PadelClub/Views/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index 2baf2bd..ab7e566 100644 --- a/PadelClub/Views/Cashier/Event/EventCreationView.swift +++ b/PadelClub/Views/Cashier/Event/EventCreationView.swift @@ -130,7 +130,8 @@ struct EventCreationView: View { private func _validate() { let event = Event(creator: Store.main.userId, name: eventName) - + event.club = selectedClub?.id + do { try dataStore.events.addOrUpdate(instance: event) } catch { @@ -152,15 +153,15 @@ struct EventCreationView: View { } - if let selectedClub, let verifiedSelectedClubId = dataStore.clubs.first(where: { selectedClub.id == $0.id })?.id { - event.club = verifiedSelectedClubId - do { - try dataStore.events.addOrUpdate(instance: event) - } catch { - Logger.error(error) - } - } - +// if let selectedClub, let verifiedSelectedClubId = dataStore.clubs.first(where: { selectedClub.id == $0.id })?.id { +// event.club = verifiedSelectedClubId +// do { +// try dataStore.events.addOrUpdate(instance: event) +// } catch { +// Logger.error(error) +// } +// } +// dismiss() navigation.path.append(tournaments.first!) diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index dee9614..3d73901 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -21,7 +21,7 @@ enum EventDestination: Identifiable, Selectable, Equatable { return String(describing: self) } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .links: return "Liens" diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 27c28ae..5e6e2af 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -33,11 +33,12 @@ struct GenericDestinationPickerView: .id("settings") } - ForEach(destinations) { destination in + ForEach(destinations.indices, id: \.self) { index in + let destination = destinations[index] Button { selectedDestination = destination } label: { - Text(destination.selectionLabel()) + Text(destination.selectionLabel(index: index)) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) } .padding() diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 7827c32..4709c0d 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -28,7 +28,7 @@ struct GroupStagesView: View { } } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .all: return "Tout" diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index 9324f15..653c5b6 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -26,22 +26,6 @@ struct MatchDateView: View { var body: some View { Menu { - Button("Ne pas jouer ce match") { - match._toggleMatchDisableState(true) - } - Button("Jouer ce match") { - match._toggleMatchDisableState(false) - } - - Button("Créer les scores") { - let teamsScores = match.getOrCreateTeamScores() - do { - try dataStore.teamScores.addOrUpdate(contentOfs: teamsScores) - } catch { - Logger.error(error) - } - } - let estimatedDuration = match.getDuration() if match.startDate == nil && isReady { Button("Démarrer") { diff --git a/PadelClub/Views/Match/Components/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift index c5a6791..8ad7a9e 100644 --- a/PadelClub/Views/Match/Components/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -14,6 +14,7 @@ struct PlayerBlockView: View { let color: Color let width: CGFloat let teamScore: TeamScore? + let isWalkOut: Bool init(match: Match, teamPosition: TeamPosition, color: Color, width: CGFloat) { self.match = match @@ -22,7 +23,9 @@ struct PlayerBlockView: View { self.team = theTeam self.color = color self.width = width - self.teamScore = match.teamScore(ofTeam: theTeam) + let theTeamScore = match.teamScore(ofTeam: theTeam) + self.teamScore = theTeamScore + self.isWalkOut = theTeamScore?.isWalkOut() == true } var names: [String]? { @@ -36,11 +39,7 @@ struct PlayerBlockView: View { var hideScore: Bool { match.hasWalkoutTeam() } - - var isWalkOut: Bool { - match.teamWalkOut(team) - } - + var scores: [String] { teamScore?.score?.components(separatedBy: ",") ?? [] } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 91a4b7a..bc6d00c 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -93,11 +93,9 @@ struct MatchDetailView: View { } } - if match.isReady() { - Section { - RowButtonView("Saisir les résultats", systemImage: "list.clipboard") { - self._editScores() - } + Section { + RowButtonView("Saisir les résultats", systemImage: "list.clipboard") { + self._editScores() } } @@ -233,6 +231,15 @@ struct MatchDetailView: View { .toolbar { ToolbarItem(placement: .topBarTrailing) { Menu { +// Button("Créer les scores") { +// let teamsScores = match.getOrCreateTeamScores() +// do { +// try dataStore.teamScores.addOrUpdate(contentOfs: teamsScores) +// } catch { +// Logger.error(error) +// } +// } + if match.courtIndex != nil { Button(role: .destructive) { match.removeCourt() @@ -433,7 +440,15 @@ struct MatchDetailView: View { } fileprivate func _editScores() { - + if match.isReady() == false && match.teams().count == 2 { + let teamsScores = match.getOrCreateTeamScores() + do { + try dataStore.teamScores.addOrUpdate(contentOfs: teamsScores) + } catch { + Logger.error(error) + } + } + self._verifyUser { self._payTournamentAndExecute { self.scoreType = .edition diff --git a/PadelClub/Views/Match/MatchRowView.swift b/PadelClub/Views/Match/MatchRowView.swift index e27e585..2012934 100644 --- a/PadelClub/Views/Match/MatchRowView.swift +++ b/PadelClub/Views/Match/MatchRowView.swift @@ -10,11 +10,12 @@ import SwiftUI struct MatchRowView: View { var match: Match let matchViewStyle: MatchViewStyle + var title: String? = nil @Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed @ViewBuilder var body: some View { - if isEditingTournamentSeed.wrappedValue == true && match.isGroupStage() == false && match.isLoserBracket == false { + if isEditingTournamentSeed.wrappedValue == true && match.isGroupStage() == false && match.disabled == false { MatchSetupView(match: match) } else { // MatchSummaryView(match: match, matchViewStyle: matchViewStyle) @@ -56,7 +57,7 @@ struct MatchRowView: View { NavigationLink { MatchDetailView(match: match, matchViewStyle: matchViewStyle) } label: { - MatchSummaryView(match: match, matchViewStyle: matchViewStyle) + MatchSummaryView(match: match, matchViewStyle: matchViewStyle, title: title) } //.modifier(BroadcastViewModifier(isBroadcasted: match.isBroadcasted())) } diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index 7c0529d..a62488a 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -18,7 +18,7 @@ struct MatchSummaryView: View { let color: Color let width: CGFloat - init(match: Match, matchViewStyle: MatchViewStyle) { + init(match: Match, matchViewStyle: MatchViewStyle, title: String? = nil) { self.match = match self.matchViewStyle = matchViewStyle self.padding = matchViewStyle == .plainStyle ? 0 : 8 @@ -34,9 +34,9 @@ struct MatchSummaryView: View { self.roundTitle = nil } - self.matchTitle = match.matchTitle(.short) + self.matchTitle = title ?? match.matchTitle(.short) - if let court = match.courtName(), match.hasEnded() == false { + if match.hasEnded() == false, let court = match.courtName() { self.courtName = court } else { self.courtName = nil @@ -54,7 +54,9 @@ struct MatchSummaryView: View { if let roundTitle { Text(roundTitle).fontWeight(.semibold) } - Text(matchTitle) + if match.index > 0 { + Text(matchTitle) + } } Spacer() if let courtName { diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index d58dc52..08f8205 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -10,73 +10,83 @@ import SwiftUI struct LoserRoundView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament + @Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed + + let loserBracket: LoserRound - let loserRounds: [Round] - @State private var isEditingTournamentSeed: Bool = false - private func _roundDisabled() -> Bool { - loserRounds.allSatisfy({ $0.isDisabled() }) +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _roundDisabled", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return loserBracket.allMatches.allSatisfy({ $0.disabled == false }) + } + + private func _matches(loserRoundId: String?) -> [Match] { + return loserBracket.allMatches.filter { $0.round == loserRoundId && (isEditingTournamentSeed.wrappedValue == true || (isEditingTournamentSeed.wrappedValue == false && $0.disabled == false)) }.sorted(by: \.index) } var body: some View { List { - if isEditingTournamentSeed == true { + if isEditingTournamentSeed.wrappedValue == true { _editingView() } - let shouldDisplayLoserRounds = loserRounds.filter({ - let matches = $0.playedMatches().filter { isEditingTournamentSeed == true || (isEditingTournamentSeed == false && $0.disabled == false) } - return matches.isEmpty == false - }).isEmpty == false - if shouldDisplayLoserRounds { - ForEach(loserRounds) { loserRound in - let matches = loserRound.playedMatches().filter { isEditingTournamentSeed == true || (isEditingTournamentSeed == false && $0.disabled == false) }.sorted(by: \.index) - if matches.isEmpty == false { - Section { - ForEach(matches) { match in - MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) - .overlay { - if match.disabled && isEditingTournamentSeed { - Image(systemName: "xmark") - .resizable() - .scaledToFit() - .opacity(0.8) - } - } - .disabled(match.disabled) - - if isEditingTournamentSeed { - RowButtonView(match.disabled ? "Jouer ce match" : "Ne pas jouer ce match", role: .destructive) { - match._toggleMatchDisableState(!match.disabled) + ForEach(loserBracket.rounds) { loserRound in + let matches = _matches(loserRoundId: loserRound.id) + if matches.isEmpty == false { + Section { + ForEach(matches) { match in + MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + .overlay { + if match.disabled && isEditingTournamentSeed.wrappedValue == true { + Image(systemName: "xmark") + .resizable() + .scaledToFit() + .opacity(0.6) } } - } - } header: { - HStack { - let labels = loserRound.roundTitleAndPointsRange(.wide, expanded: isEditingTournamentSeed, inTournament: tournament) - if let seedIntervalLocalizedLabel = labels.0 { - Text(seedIntervalLocalizedLabel) - } - if let seedIntervalPointRange = labels.1 { - Spacer() - Text(seedIntervalPointRange) - .font(.caption) + .disabled(match.disabled) + + if isEditingTournamentSeed.wrappedValue == true { + RowButtonView(match.disabled ? "Jouer ce match" : "Ne pas jouer ce match", role: .destructive) { + match._toggleMatchDisableState(!match.disabled) } } } + } header: { + HStack { + if let seedInterval = loserRound.seedInterval() { + Text(seedInterval.localizedLabel(.wide)) + let seedIntervalPointRange = seedInterval.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournament.teamCount) + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } } } + } + + /* + let shouldDisplayLoserRounds : Bool = isEditingTournamentSeed.wrappedValue == true ? true : (allMatches.first(where: { $0.disabled == false }) != nil) + + if shouldDisplayLoserRounds { } else { Section { ContentUnavailableView("Aucun match joué", systemImage: "tennisball", description: Text("Il n'y aucun match à jouer dans ce tour de match de classement.")) } } + */ } .headerProminence(.increased) .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button(isEditingTournamentSeed == true ? "Valider" : "Modifier") { - isEditingTournamentSeed.toggle() + Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { + isEditingTournamentSeed.wrappedValue.toggle() } } } @@ -85,13 +95,13 @@ struct LoserRoundView: View { private func _editingView() -> some View { if _roundDisabled() { RowButtonView("Jouer ce tour", role: .destructive) { - loserRounds.forEach { round in + loserBracket.rounds.forEach { round in round.enableRound() } } } else { RowButtonView("Ne pas jouer ce tour", role: .destructive) { - loserRounds.forEach { round in + loserBracket.rounds.forEach { round in round.disableRound() } } diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index c7643e2..948d2ea 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -7,27 +7,118 @@ import SwiftUI +class UpperRound: Identifiable, Selectable { + var id: String { round.id } + let round: Round + lazy var loserRounds: [LoserRound] = { + LoserRound.updateDestinations(fromLoserRounds: round.loserRounds(), inUpperBracketRound: round) + }() + let title: String + let playedMatches: [Match] + let correspondingLoserRoundTitle: String + + init(round: Round) { + self.round = round + self.title = round.roundTitle(.short) + self.playedMatches = round.playedMatches() + self.correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() + } + + func loserMatches() -> [Match] { + loserRounds.flatMap({ $0.allMatches }).filter({ $0.disabled == false }) + } + + func status() -> (Int, Int) { + let loserMatches = loserMatches() + return (loserMatches.filter { $0.hasEnded() }.count, loserMatches.count) + } +} + +extension UpperRound: Equatable { + static func == (lhs: UpperRound, rhs: UpperRound) -> Bool { + lhs.id == rhs.id + } + + func selectionLabel(index: Int) -> String { + return title + } + + func badgeValue() -> Int? { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeValue round of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return playedMatches.filter({ $0.isRunning() }).count + } + + func badgeValueColor() -> Color? { + return nil + } + + func badgeImage() -> Badge? { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeImage of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + return playedMatches.allSatisfy({ $0.hasEnded() }) ? .checkmark : nil + } +} + + struct LoserRound: Identifiable, Selectable { let turnIndex: Int let rounds: [Round] + let allMatches: [Match] + + init(turnIndex: Int, rounds: [Round]) { + self.turnIndex = turnIndex + self.rounds = rounds + self.allMatches = rounds.flatMap { $0.playedMatches() } + } var id: Int { return turnIndex } - + var shouldBeDisplayed: Bool { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func shouldBeDisplayed loserRound", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + return allMatches.first(where: { $0.disabled == false }) != nil + } + static func updateDestinations(fromLoserRounds loserRounds: [Round], inUpperBracketRound upperBracketRound: Round) -> [LoserRound] { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func updateDestinations(fromLoserRounds", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif var rounds = [LoserRound]() + let allLoserRounds = upperBracketRound.loserRoundsAndChildren() for (index, round) in loserRounds.enumerated() { - rounds.append(LoserRound(turnIndex: index, rounds: upperBracketRound.loserRounds(forRoundIndex: round.index))) + rounds.append(LoserRound(turnIndex: index, rounds: upperBracketRound.loserRounds(forRoundIndex: round.index, loserRoundsAndChildren: allLoserRounds))) } return rounds } static func enabledLoserRounds(inLoserRounds loserRounds: [Round], inUpperBracketRound upperBracketRound: Round) -> [Round] { + let allLoserRounds = upperBracketRound.loserRoundsAndChildren() return loserRounds.filter { loserRound in - upperBracketRound.loserRounds(forRoundIndex: loserRound.index).anySatisfy({ $0.isDisabled() == false }) + upperBracketRound.loserRounds(forRoundIndex: loserRound.index, loserRoundsAndChildren: allLoserRounds).anySatisfy({ $0.isDisabled() == false }) } } @@ -40,13 +131,15 @@ extension LoserRound: Equatable { } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { + if index < turnIndex { + return "Tour #\(index + 1)" + } return "Tour #\(turnIndex + 1)" } func badgeValue() -> Int? { - let playedMatches: [Match] = self.rounds.flatMap { $0.playedMatches() } - let runningMatches: [Match] = playedMatches.filter { $0.isRunning() } + let runningMatches: [Match] = allMatches.filter { $0.disabled == false && $0.isRunning() } return runningMatches.count } @@ -55,32 +148,45 @@ extension LoserRound: Equatable { } func badgeImage() -> Badge? { - return rounds.allSatisfy({ $0.hasEnded() }) ? .checkmark : nil +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func badgeImage loserRound", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + return allMatches.filter { $0.disabled == false }.allSatisfy({ $0.hasEnded() }) ? .checkmark : nil } } struct LoserRoundsView: View { - var upperBracketRound: Round + var upperBracketRound: UpperRound @State private var selectedRound: LoserRound? - let loserRounds: [Round] - @State private var allDestinations: [LoserRound] - - init(upperBracketRound: Round) { + @State private var isEditingTournamentSeed = false + + init(upperBracketRound: UpperRound) { self.upperBracketRound = upperBracketRound - let _loserRounds = upperBracketRound.loserRounds() - self.loserRounds = _loserRounds - let rounds = LoserRound.updateDestinations(fromLoserRounds: _loserRounds, inUpperBracketRound: upperBracketRound) - _allDestinations = State(wrappedValue: rounds) - - _selectedRound = State(wrappedValue: rounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? rounds.first) + _selectedRound = State(wrappedValue: upperBracketRound.loserRounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? upperBracketRound.loserRounds.first(where: { $0.shouldBeDisplayed })) + } + + var destinations: [LoserRound] { + isEditingTournamentSeed ? upperBracketRound.loserRounds : upperBracketRound.loserRounds.filter({ $0.shouldBeDisplayed }) } var body: some View { VStack(spacing: 0) { - GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: allDestinations, nilDestinationIsValid: false) - LoserRoundView(loserRounds: selectedRound!.rounds) + GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: false) + if let selectedRound { + LoserRoundView(loserBracket: selectedRound) + } else { + Section { + ContentUnavailableView("Aucun tour à jouer", systemImage: "tennisball", description: Text("Il il n'y a aucun tour de match de classement prévu.")) + } + } } + .environment(\.isEditingTournamentSeed, $isEditingTournamentSeed) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index fe3c15b..eaa84ba 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -21,16 +21,32 @@ struct RoundView: View { @State private var availableSeedGroup: SeedInterval? @State private var showPrintScreen: Bool = false - var round: Round + var upperRound: UpperRound private func _getAvailableSeedGroup() async { - availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _getAvailableSeedGroup of: ", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + + availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: upperRound.round.index) } private func _getSpaceLeft() async { +#if DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _getSpaceLeft of: ", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } +#endif + self.spaceLeft.removeAll() self.seedSpaceLeft.removeAll() - let displayableMatches: [Match] = self.round.displayableMatches() + let displayableMatches: [Match] = self.upperRound.round.displayableMatches() displayableMatches.forEach { match in let count: Int = match.teamScores.count if count == 0 { @@ -52,8 +68,8 @@ struct RoundView: View { var body: some View { List { - let displayableMatches = round.displayableMatches().sorted(by: \.index) - let loserRounds = round.loserRounds() + let displayableMatches = upperRound.round.displayableMatches().sorted(by: \.index) + let loserRounds = upperRound.loserRounds if displayableMatches.isEmpty { Section { ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball") @@ -66,30 +82,38 @@ struct RoundView: View { } .tipStyle(tint: .master, asSection: true) - if loserRounds.isEmpty == false { - let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() + if upperRound.loserRounds.isEmpty == false { Section { NavigationLink { - LoserRoundsView(upperBracketRound: round) + LoserRoundsView(upperBracketRound: upperRound) .environment(tournament) - .navigationTitle(correspondingLoserRoundTitle) + .navigationTitle(upperRound.correspondingLoserRoundTitle) } label: { - Text(correspondingLoserRoundTitle) + LabeledContent { + let status = upperRound.status() + if status.0 == status.1 { + Image(systemName: "checkmark").foregroundStyle(.green) + } else { + Text("\(status.0) terminé\(status.0.pluralSuffix) sur \(status.1)") + } + } label: { + Text(upperRound.correspondingLoserRoundTitle) + } } } } } else { let disabledMatchesCount = BracketEditTip.matchesHidden if disabledMatchesCount > 0 { - let bracketTip = BracketEditTip(nextRoundName: round.nextRound()?.roundTitle()) + let bracketTip = BracketEditTip(nextRoundName: upperRound.round.nextRound()?.roundTitle()) TipView(bracketTip).tipStyle(tint: .green, asSection: true) Section { - let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: round.index) - disabledMatchesCount) + let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: upperRound.round.index) - disabledMatchesCount) LabeledContent { Text(leftToPlay.formatted()).font(.largeTitle) } label: { - Text("Match\(leftToPlay.pluralSuffix) à jouer \(round.roundTitle(.short))") + Text("Match\(leftToPlay.pluralSuffix) à jouer \(upperRound.title)") Text("\(disabledMatchesCount) match\(disabledMatchesCount.pluralSuffix) désactivé\(disabledMatchesCount.pluralSuffix) automatiquement") } } @@ -101,7 +125,7 @@ struct RoundView: View { if availableSeeds.isEmpty == false, let availableSeedGroup { Section { RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { - tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) + tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) await _save() if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { self.isEditingTournamentSeed.wrappedValue = false @@ -225,16 +249,17 @@ struct RoundView: View { } ForEach(displayableMatches) { match in + let matchTitle = match.matchTitle(.short, inMatches: displayableMatches) Section { - MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle, title: matchTitle) } header: { HStack { - Text(round.roundTitle(.wide)) - if round.index > 0 { - Text(match.matchTitle(.short, inMatches: displayableMatches)) + Text(upperRound.round.roundTitle(.wide)) + if upperRound.round.index > 0 { + Text(matchTitle) } else { let tournamentTeamCount = tournament.teamCount - if let seedIntervalPointRange = round.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + if let seedIntervalPointRange = upperRound.round.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { Spacer() Text(seedIntervalPointRange) .font(.caption) @@ -257,15 +282,15 @@ struct RoundView: View { Task { await _prepareRound() } - let seeds = round.seeds() + let seeds = upperRound.round.seeds() SlideToDeleteSeedTip.seeds = seeds.count PrintTip.seeds = seeds.count - BracketEditTip.matchesHidden = round.getDisabledMatches().count + BracketEditTip.matchesHidden = upperRound.round.getDisabledMatches().count } .fullScreenCover(isPresented: showVisualDrawView) { if let availableSeedGroup = selectedSeedGroup { let seeds = tournament.seeds(inSeedGroup: availableSeedGroup) - let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: round.index) + let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: upperRound.round.index) NavigationStack { SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in Task { diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift index 1647906..913893e 100644 --- a/PadelClub/Views/Round/RoundsView.swift +++ b/PadelClub/Views/Round/RoundsView.swift @@ -9,16 +9,20 @@ import SwiftUI struct RoundsView: View { var tournament: Tournament - @State private var selectedRound: Round? + @State private var selectedRound: UpperRound? @State private var isEditingTournamentSeed = false + let destinations: [UpperRound] + init(tournament: Tournament) { self.tournament = tournament + let _destinations = tournament.rounds().map { UpperRound(round: $0) } + self.destinations = _destinations let availableSeeds = tournament.availableSeeds() if tournament.shouldVerifyBracket && availableSeeds.isEmpty { _selectedRound = State(wrappedValue: nil) } else { - _selectedRound = State(wrappedValue: tournament.getActiveRound()) + _selectedRound = State(wrappedValue: _destinations.first(where: { $0.id == tournament.getActiveRound()?.id })) } if availableSeeds.isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false { _isEditingTournamentSeed = State(wrappedValue: true) @@ -27,14 +31,14 @@ struct RoundsView: View { var body: some View { VStack(spacing: 0) { - GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: tournament.rounds(), nilDestinationIsValid: true) + GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: true) switch selectedRound { case .none: RoundSettingsView() .navigationTitle("Réglages") case .some(let selectedRound): - RoundView(round: selectedRound).id(selectedRound.id) - .navigationTitle(selectedRound.roundTitle()) + RoundView(upperRound: selectedRound).id(selectedRound.id) + .navigationTitle(selectedRound.round.roundTitle()) } } .environment(\.isEditingTournamentSeed, $isEditingTournamentSeed) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 9795143..ad5b8ca 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -137,7 +137,7 @@ struct InscriptionManagerView: View { } private func _setHash() async { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -431,7 +431,7 @@ struct InscriptionManagerView: View { } private func _prepareStats() async { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -446,7 +446,7 @@ struct InscriptionManagerView: View { } private func _prepareTeams() { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -472,7 +472,7 @@ struct InscriptionManagerView: View { } private func _getIssues() async { - #if DEBUG_TIME + #if DEBUG_TIME //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) diff --git a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift index 5e5024e..4afa906 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift @@ -26,7 +26,7 @@ enum CallDestination: Identifiable, Selectable, Equatable { } } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .seeds: return "Têtes de série" diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 5e0aef3..01468f9 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -32,14 +32,14 @@ enum CashierDestination: Identifiable, Selectable, Equatable { } } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .summary: return "Bilan" case .groupStage(let groupStage): - return groupStage.selectionLabel() + return groupStage.selectionLabel(index: index) case .bracket(let round): - return round.selectionLabel() + return round.selectionLabel(index: index) case .all: return "Tous" } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 476353b..08c655b 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -15,7 +15,9 @@ struct TournamentRankView: View { @State private var rankings: [Int: [TeamRegistration]] = [:] @State private var calculating = false @State private var selectedTeam: TeamRegistration? - + @State private var runningMatches: [Match]? + @State private var matchesLeft: [Match]? + var isEditingTeam: Binding { Binding { selectedTeam != nil @@ -26,11 +28,24 @@ struct TournamentRankView: View { var body: some View { List { @Bindable var tournament = tournament - let matchs = tournament.runningMatches(tournament.allMatches()) let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil }) Section { LabeledContent { - Text(matchs.count.formatted()) + if let matchesLeft { + Text(matchesLeft.count.formatted()) + } else { + ProgressView() + } + } label: { + Text("Matchs restant") + } + + LabeledContent { + if let runningMatches { + Text(runningMatches.count.formatted()) + } else { + ProgressView() + } } label: { Text("Matchs en cours") } @@ -110,6 +125,11 @@ struct TournamentRankView: View { } } } + .task { + let all = tournament.allMatches() + self.runningMatches = await tournament.asyncRunningMatches(all) + self.matchesLeft = await tournament.readyMatches(all) + } .alert("Position", isPresented: isEditingTeam) { if let selectedTeam { @Bindable var team = selectedTeam diff --git a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift index addd345..9e4c965 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift @@ -31,7 +31,7 @@ enum ScheduleDestination: String, Identifiable, Selectable, Equatable { case scheduleGroupStage case scheduleBracket - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .scheduleGroupStage: return "Poules" diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index 6a2574b..9a819bd 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -19,7 +19,7 @@ enum TournamentSettings: Identifiable, Selectable, Equatable { var id: String { String(describing: self) } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { switch self { case .status: return "Statut" diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index c57c363..e99b7f6 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -39,9 +39,6 @@ struct TournamentView: View { var body: some View { VStack(spacing: 0.0) { List { - TipView(tournamentRunningTip) - .tipStyle(tint: nil) - if tournament.state() != .finished { SubscriptionInfoView() } @@ -188,6 +185,7 @@ struct TournamentView: View { } } label: { LabelOptions() + .popoverTip(tournamentRunningTip) } } } diff --git a/PadelClub/Views/User/LoginView.swift b/PadelClub/Views/User/LoginView.swift index cbfdf6a..937bd98 100644 --- a/PadelClub/Views/User/LoginView.swift +++ b/PadelClub/Views/User/LoginView.swift @@ -85,7 +85,7 @@ struct LoginView: View { ContentUnavailableView { Label("Vérifiez vos emails.", systemImage: "envelope.badge") } description: { - Text("Vous pouvez maintenant ouvrir votre boîte mail pour valider votre compte. Vous pourrez ensuite vous connecter ici. N'oubliez pas de vérifiez vos spams !") + Text("Vous pourrez ensuite vous connecter ici. N'oubliez pas de vérifiez vos spams !") } actions: { SupportButtonView(contentIsUnavailable: true) } From 89d62ae118cf5511ea793dbce1724197e6c13692 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 25 Jun 2024 20:19:55 +0200 Subject: [PATCH 03/10] v69 --- PadelClub.xcodeproj/project.pbxproj | 53 ++--------------------------- PadelClub/GoogleService-Info.plist | 30 ---------------- PadelClub/PadelClubApp.swift | 9 ----- 3 files changed, 2 insertions(+), 90 deletions(-) delete mode 100644 PadelClub/GoogleService-Info.plist diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 9888ad1..efdee37 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -238,8 +238,6 @@ FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; - FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */; }; - FFE2D2D82C216D4800D0C7BE /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */; }; FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; @@ -568,7 +566,6 @@ FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = ""; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = ""; }; - FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = ""; }; FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = ""; }; FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -595,7 +592,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */, FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */, FF92660D2C241CE0002361A4 /* Zip in Frameworks */, C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */, @@ -644,7 +640,6 @@ isa = PBXGroup; children = ( FF92660F2C255E4A002361A4 /* PadelClub.entitlements */, - FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */, FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */, FFA6D78A2BB0BEB3003A31F3 /* Info.plist */, C4EC6F562BE92CAC000CEAB4 /* local.plist */, @@ -1340,7 +1335,6 @@ C425D3FA2B6D249D002A7B48 /* Frameworks */, C425D3FB2B6D249D002A7B48 /* Resources */, FF2BE4892B85E27400592328 /* Embed Frameworks */, - FFE2D2D62C216C1700D0C7BE /* ShellScript */, ); buildRules = ( ); @@ -1349,7 +1343,6 @@ name = PadelClub; packageProductDependencies = ( FFCFBFFD2BBBE86600B82851 /* Algorithms */, - FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */, FF92660C2C241CE0002361A4 /* Zip */, ); productName = PadelClub; @@ -1427,7 +1420,6 @@ mainGroup = C425D3F42B6D249D002A7B48; packageReferences = ( FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */, - FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */, ); productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */; @@ -1457,7 +1449,6 @@ FF1F4B892BFA02A4000B4573 /* groupstagecol-template.html in Resources */, FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */, FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */, - FFE2D2D82C216D4800D0C7BE /* GoogleService-Info.plist in Resources */, FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */, FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */, C45BAE3B2BC6DF10002EEC8A /* SyncedProducts.storekit in Resources */, @@ -1483,31 +1474,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - FFE2D2D62C216C1700D0C7BE /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}", - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", - "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", - "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ C425D3F92B6D249D002A7B48 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1912,7 +1878,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 69; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1936,7 +1902,6 @@ ); MARKETING_VERSION = 0.1; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_LDFLAGS = "-ObjC"; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1953,7 +1918,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 69; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1976,7 +1941,6 @@ ); MARKETING_VERSION = 0.1; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_LDFLAGS = "-ObjC"; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=5 -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=50"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2120,14 +2084,6 @@ minimumVersion = 2.1.2; }; }; - FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 10.28.0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -2141,11 +2097,6 @@ package = FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */; productName = Algorithms; }; - FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */ = { - isa = XCSwiftPackageProductDependency; - package = FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseCrashlytics; - }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/PadelClub/GoogleService-Info.plist b/PadelClub/GoogleService-Info.plist deleted file mode 100644 index eae9408..0000000 --- a/PadelClub/GoogleService-Info.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - API_KEY - AIzaSyAjZC_NmXtQzK5dotExlQjU66fTNylNMII - GCM_SENDER_ID - 404879692726 - PLIST_VERSION - 1 - BUNDLE_ID - app.padelclub - PROJECT_ID - padel-club-8e872 - STORAGE_BUCKET - padel-club-8e872.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:404879692726:ios:90e65a062285ac3dd46717 - - \ No newline at end of file diff --git a/PadelClub/PadelClubApp.swift b/PadelClub/PadelClubApp.swift index 92d42d4..37b0a66 100644 --- a/PadelClub/PadelClubApp.swift +++ b/PadelClub/PadelClubApp.swift @@ -8,7 +8,6 @@ import SwiftUI import LeStorage import TipKit -import FirebaseCore @main struct PadelClubApp: App { @@ -16,7 +15,6 @@ struct PadelClubApp: App { @State private var navigationViewModel = NavigationViewModel() @StateObject var networkMonitor: NetworkMonitor = NetworkMonitor() @StateObject var dataStore = DataStore.shared - @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate @State private var registrationError: RegistrationError? = nil var presentError: Binding { @@ -148,10 +146,3 @@ struct PadelClubApp: App { } } } - -class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - FirebaseApp.configure() - return true - } -} From ea0159e3df47ff7c9f90e81709dfb15e493b0c10 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 26 Jun 2024 09:34:34 +0200 Subject: [PATCH 04/10] fix text stuff --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/Round.swift | 4 +- PadelClub/Data/Tournament.swift | 38 +++++---- PadelClub/Utils/PadelRule.swift | 2 +- .../Views/Calling/CallSettingsView.swift | 2 +- PadelClub/Views/Planning/SchedulerView.swift | 40 +++++---- .../Screen/TournamentScheduleView.swift | 2 +- .../Tournament/TournamentBuildView.swift | 82 +++++++++---------- .../Views/Tournament/TournamentInitView.swift | 10 ++- 9 files changed, 99 insertions(+), 85 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index efdee37..500ba92 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1878,7 +1878,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 69; + CURRENT_PROJECT_VERSION = 71; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1918,7 +1918,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 69; + CURRENT_PROJECT_VERSION = 71; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 881f649..60a137f 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -36,7 +36,7 @@ class Round: ModelObject, Storable { } func _matches() -> [Match] { - return DataStore.shared.matches.filter { $0.round == self.id } + return DataStore.shared.matches.filter { $0.round == self.id }.sorted(by: \.index) } func getDisabledMatches() -> [Match] { @@ -249,7 +249,7 @@ defer { } func enabledMatches() -> [Match] { - return DataStore.shared.matches.filter { $0.round == self.id && $0.disabled == false } + return DataStore.shared.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index) } func displayableMatches() -> [Match] { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 5a0ea1d..eee8c5f 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1394,20 +1394,28 @@ defer { return TournamentStatus(label: label, completion: completionLabel) } - func bracketStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) { + func bracketStatus() async -> (status: String, description: String?, cut: TeamRegistration.TeamRange?) { let availableSeeds = availableSeeds() - let cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) + var description: String? = nil if availableSeeds.isEmpty == false { - return ("placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série", cut) + description = "placer \(availableSeeds.count) équipe\(availableSeeds.count.pluralSuffix)" } - let availableQualifiedTeams = availableQualifiedTeams() - if availableQualifiedTeams.isEmpty == false { - return ("placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix, cut) + if description == nil { + let availableQualifiedTeams = availableQualifiedTeams() + if availableQualifiedTeams.isEmpty == false { + description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix + } + } + + var cut: TeamRegistration.TeamRange? = nil + if description == nil && isAnimation() == false { + cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) } + if let round = getActiveRound() { - return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " "), cut) + return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut) } else { - return ("à construire", nil) + return ("", description, nil) } } @@ -1415,10 +1423,10 @@ defer { let groupStageTeams = groupStageTeams() let groupStageTeamsCount = groupStageTeams.count if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() { - return ("à faire", nil) + return ("à compléter", nil) } - let cut = TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last) + let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last) let runningGroupStages = groupStages().filter({ $0.isRunning() }) if groupStagesAreOver() { return ("terminées", cut) } @@ -1435,16 +1443,12 @@ defer { } func settingsDescriptionLocalizedLabel() -> String { - [dayDuration.formatted() + " jour\(dayDuration.pluralSuffix)", courtCount.formatted() + " terrain\(courtCount.pluralSuffix)"].joined(separator: ", ") + [courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ") } func structureDescriptionLocalizedLabel() -> String { - if state() == .initial { - return "à valider" - } else { - let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil - return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") - } + let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil + return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ") } func deleteAndBuildEverything() { diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index e14f1fd..2a6afa8 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -342,7 +342,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { return .twoSetsDecisivePointSuperTie } default: - return .twoSetsDecisivePoint + return .superTie } } diff --git a/PadelClub/Views/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index 34bd12d..027945d 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -46,7 +46,7 @@ struct CallSettingsView: View { Logger.error(error) } } label: { - Text(.init("Le tournoi n'est pas visible sur [Padel Club](\(URLs.main.rawValue)), ")).foregroundStyle(.logoRed) + Text("le rendre visible ?").underline().foregroundStyle(.master) + Text(.init("Le tournoi est privée, le publier ?")).underline().foregroundStyle(.master) } } } diff --git a/PadelClub/Views/Planning/SchedulerView.swift b/PadelClub/Views/Planning/SchedulerView.swift index 924720d..4b9fcc6 100644 --- a/PadelClub/Views/Planning/SchedulerView.swift +++ b/PadelClub/Views/Planning/SchedulerView.swift @@ -50,13 +50,15 @@ struct SchedulerView: View { } } footer: { - if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight { - Button { - tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat() - } label: { - Text("devrait être joué au moins en " + tournament.groupStageSmartMatchFormat().format + ", ") + Text("modifier ?").underline().foregroundStyle(.master) + if tournament.isAnimation() == false { + if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight { + Button { + tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat() + } label: { + Text("devrait être joué au moins en " + tournament.groupStageSmartMatchFormat().format + ", ") + Text("modifier ?").underline().foregroundStyle(.master) + } + .buttonStyle(.plain) } - .buttonStyle(.plain) } } @@ -117,19 +119,21 @@ struct SchedulerView: View { } header: { Text(round.titleLabel()) } footer: { - let federalFormat = tournament.roundSmartMatchFormat(round.index) - if round.matchFormat.weight > federalFormat.weight { - Button { - round.updateMatchFormatAndAllMatches(federalFormat) - do { - try dataStore.rounds.addOrUpdate(instance: round) - } catch { - Logger.error(error) + if tournament.isAnimation() == false { + let federalFormat = tournament.roundSmartMatchFormat(round.index) + if round.matchFormat.weight > federalFormat.weight { + Button { + round.updateMatchFormatAndAllMatches(federalFormat) + do { + try dataStore.rounds.addOrUpdate(instance: round) + } catch { + Logger.error(error) + } + } label: { + Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master) } - } label: { - Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master) + .buttonStyle(.plain) } - .buttonStyle(.plain) } } @@ -160,7 +164,7 @@ struct SchedulerView: View { } header: { Text("Match de classement \(round.roundTitle(.short))") } footer: { - if round.index == 1, let semi = round.loserRounds().first { + if tournament.isAnimation() == false, round.index == 1, let semi = round.loserRounds().first { let federalFormat = tournament.loserBracketSmartMatchFormat(1) if semi.matchFormat.weight > federalFormat.weight { Button { diff --git a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift index 9e4c965..bb7bb55 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift @@ -92,7 +92,7 @@ struct TournamentScheduleView: View { } .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - .navigationTitle("Horaires") + .navigationTitle("Horaires et formats") } } diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 7f4954d..9852f12 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -9,7 +9,7 @@ import SwiftUI struct TournamentBuildView: View { var tournament: Tournament - @State private var bracketStatus: (status: String, cut: TeamRegistration.TeamRange?)? + @State private var bracketStatus: (status: String, description: String?, cut: TeamRegistration.TeamRange?)? @State private var groupStageStatus: (String, TeamRegistration.TeamRange?)? @State private var callStatus: Tournament.TournamentStatus? @State private var scheduleStatus: Tournament.TournamentStatus? @@ -65,7 +65,9 @@ struct TournamentBuildView: View { Text("Tableau") if tournament.shouldVerifyBracket { Text("Vérifier la tableau").foregroundStyle(.logoRed) - } else if let range = bracketStatus?.1 { + } else if let description = bracketStatus?.1 { + Text(description) + } else if let range = bracketStatus?.2 { HStack { if let left = range.left { Text(left.weight.formatted()) @@ -98,50 +100,48 @@ struct TournamentBuildView: View { TournamentBroadcastRowView(tournament: tournament) } - if state != .finished { - NavigationLink(value: Screen.schedule) { - let tournamentStatus = scheduleStatus - LabeledContent { - if let tournamentStatus { - Text(tournamentStatus.completion) - } else { - ProgressView() - } - } label: { - Text("Horaires") - if let tournamentStatus { - Text(tournamentStatus.label).lineLimit(1) - } else { - Text(" ") - } + NavigationLink(value: Screen.schedule) { + let tournamentStatus = scheduleStatus + LabeledContent { + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() } - } - .task { - scheduleStatus = await tournament.scheduleStatus() - } - - NavigationLink(value: Screen.call) { - let tournamentStatus = callStatus - LabeledContent { - if let tournamentStatus { - Text(tournamentStatus.completion) - } else { - ProgressView() - } - } label: { - Text("Convocations") - if let tournamentStatus { - Text(tournamentStatus.label).lineLimit(1) - } else { - Text(" ") - } + } label: { + Text("Horaires et formats") + if let tournamentStatus { + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") } } - .task { - callStatus = await tournament.callStatus() - } + } + .task { + scheduleStatus = await tournament.scheduleStatus() } + NavigationLink(value: Screen.call) { + let tournamentStatus = callStatus + LabeledContent { + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() + } + } label: { + Text("Convocations") + if let tournamentStatus { + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") + } + } + } + .task { + callStatus = await tournament.callStatus() + } + NavigationLink(value: Screen.cashier) { let tournamentStatus = cashierStatus LabeledContent { diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index 6bd6faf..f0de978 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -28,17 +28,23 @@ struct TournamentInitView: View { NavigationLink(value: Screen.structure) { LabeledContent { - Text(tournament.structureDescriptionLocalizedLabel()) + if tournament.state() == .initial { + Text("à valider") + } else { + Image(systemName: "checkmark").foregroundStyle(.green) + } } label: { LabelStructure() + Text(tournament.structureDescriptionLocalizedLabel()) } } NavigationLink(value: Screen.settings) { LabeledContent { - Text(tournament.settingsDescriptionLocalizedLabel()) + Text(tournament.tournamentTitle(.short)) } label: { LabelSettings() + Text("Formats, statut, club, prix, etc.") } } From 44f353e24da4821832d5a69615dd84a915e0412a Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 26 Jun 2024 11:47:56 +0200 Subject: [PATCH 05/10] =?UTF-8?q?ajout=20de=20la=20visibilit=C3=A9=20des?= =?UTF-8?q?=20joueurs=20qui=20n'ont=20pas=20de=20moyen=20d'=C3=AAtre=20con?= =?UTF-8?q?tact=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PadelClub.xcodeproj/project.pbxproj | 8 ++- PadelClub/Extensions/String+Extensions.swift | 6 ++ PadelClub/Utils/FileImportManager.swift | 10 ++-- .../PlayersWithoutContactView.swift | 55 +++++++++++++++++++ .../Views/Calling/GroupStageCallingView.swift | 2 + .../Views/Calling/SeedsCallingView.swift | 5 +- PadelClub/Views/Club/ClubsView.swift | 2 +- PadelClub/Views/Round/RoundView.swift | 1 - .../Views/Tournament/FileImportView.swift | 6 ++ .../Components/InscriptionInfoView.swift | 14 ++--- 10 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 500ba92..4ed8b55 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -225,6 +225,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 */; }; + FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FFCFBFFD2BBBE86600B82851 /* Algorithms */; }; FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; }; FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; }; @@ -553,6 +554,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 = ""; }; + FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = ""; }; FFCFC0012BBC39A600B82851 /* EditScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScoreView.swift; sourceTree = ""; }; FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointSelectionView.swift; sourceTree = ""; }; FFCFC0112BBC3E1A00B82851 /* PointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointView.swift; sourceTree = ""; }; @@ -1251,6 +1253,7 @@ isa = PBXGroup; children = ( FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */, + FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */, ); path = Components; sourceTree = ""; @@ -1512,6 +1515,7 @@ FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, + FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */, FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, @@ -1878,7 +1882,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 71; + CURRENT_PROJECT_VERSION = 72; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1918,7 +1922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 71; + CURRENT_PROJECT_VERSION = 72; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Extensions/String+Extensions.swift b/PadelClub/Extensions/String+Extensions.swift index 580830e..a0abca5 100644 --- a/PadelClub/Extensions/String+Extensions.swift +++ b/PadelClub/Extensions/String+Extensions.swift @@ -195,3 +195,9 @@ extension String { return url } } + +extension String { + func toInt() -> Int? { + Int(self) + } +} diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index 1fc4b7b..b27ee9c 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -390,16 +390,18 @@ class FileImportManager { var teamName: String? = nil let players = team.map { player in let data = player.components(separatedBy: separator) - let firstName : String = data[safe: 2]?.trimmed ?? "" - let lastName : String = data[safe: 3]?.trimmed ?? "" + let lastName : String = data[safe: 2]?.trimmed ?? "" + let firstName : String = data[safe: 3]?.trimmed ?? "" let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male if data[safe: 1]?.trimmed != nil { teamName = data[safe: 1]?.trimmed } let phoneNumber : String? = data[safe: 4]?.trimmed let email : String? = data[safe: 5]?.trimmed - //let level : String? = data[safe: 6]?.trimmed - let player = PlayerRegistration(firstName: firstName, lastName: lastName, sex: sex, phoneNumber: phoneNumber, email: email) + let rank : Int? = data[safe: 6]?.trimmed.toInt() + let licenceId : String? = data[safe: 7]?.trimmed + let club : String? = data[safe: 8]?.trimmed + let player = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: licenceId, rank: rank, sex: sex, clubName: club, phoneNumber: phoneNumber, email: email) return player } diff --git a/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift b/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift new file mode 100644 index 0000000..6b75e8c --- /dev/null +++ b/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift @@ -0,0 +1,55 @@ +// +// PlayersWithoutContactView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 26/06/2024. +// + +import SwiftUI + +struct PlayersWithoutContactView: View { + @Environment(Tournament.self) var tournament: Tournament + let players: [PlayerRegistration] + + var body: some View { + Section { + let withoutEmails = players.filter({ $0.email?.isEmpty == true }) + DisclosureGroup { + ForEach(withoutEmails) { player in + NavigationLink { + PlayerDetailView(player: player) + .environment(tournament) + } label: { + ImportedPlayerView(player: player) + } + } + } label: { + LabeledContent { + Text(withoutEmails.count.formatted()) + } label: { + Text("Joueurs sans email") + } + } + + let withoutPhones = players.filter({ $0.phoneNumber?.isEmpty == true }) + DisclosureGroup { + ForEach(withoutPhones) { player in + NavigationLink { + PlayerDetailView(player: player) + .environment(tournament) + } label: { + ImportedPlayerView(player: player) + } + } + } label: { + LabeledContent { + Text(withoutPhones.count.formatted()) + } label: { + Text("Joueurs sans téléphone") + } + } + } header: { + Text("Joueurs sans moyen de contact") + } + } +} diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 396b586..03d333b 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -14,6 +14,8 @@ struct GroupStageCallingView: View { var body: some View { let groupStages = tournament.groupStages() List { + PlayersWithoutContactView(players: groupStages.flatMap({ $0.unsortedTeams() }).flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) + _sameTimeGroupStageView(groupStages: groupStages) ForEach(groupStages) { groupStage in diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index 806bc30..8ab7110 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -13,7 +13,10 @@ struct SeedsCallingView: View { var body: some View { List { - ForEach(tournament.rounds()) { round in + let tournamentRounds = tournament.rounds() + PlayersWithoutContactView(players: tournament.seededTeams().flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) + + ForEach(tournamentRounds) { round in let seeds = round.seeds() let callSeeds = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) if seeds.isEmpty == false { diff --git a/PadelClub/Views/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index 913d8f7..8cf0620 100644 --- a/PadelClub/Views/Club/ClubsView.swift +++ b/PadelClub/Views/Club/ClubsView.swift @@ -30,7 +30,7 @@ struct ClubsView: View { #if DEBUG Section { RowButtonView("Delete unexisted clubs", action: { - var ids = dataStore.user.clubs + let ids = dataStore.user.clubs ids.forEach { clubId in if dataStore.clubs.findById(clubId) == nil { dataStore.user.clubs.removeAll(where: { $0 == clubId }) diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index eaa84ba..e36dbc0 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -69,7 +69,6 @@ struct RoundView: View { var body: some View { List { let displayableMatches = upperRound.round.displayableMatches().sorted(by: \.index) - let loserRounds = upperRound.loserRounds if displayableMatches.isEmpty { Section { ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball") diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 1eae621..0641b19 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -16,6 +16,8 @@ enum FileImportCustomField: Int, Identifiable, CaseIterable { case teamName case lastName case firstName + case phoneNumber + case email case rank case licenceId case clubName @@ -44,6 +46,10 @@ enum FileImportCustomField: Int, Identifiable, CaseIterable { return "Nom" case .firstName: return "Prénom" + case .phoneNumber: + return "Téléphone" + case .email: + return "E-mail" case .rank: return "Rang" case .licenceId: diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 80fc68b..aaf6d49 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -43,13 +43,13 @@ struct InscriptionInfoView: View { } .listRowView(color: .cyan) - LabeledContent { - Text(selectedTeams.filter { $0.confirmed() }.count.formatted()) - } label: { - Text("Paires ayant confirmées") - Text("Vous avez noté la confirmation de l'équipe") - } - .listRowView(color: .green) +// LabeledContent { +// Text(selectedTeams.filter { $0.confirmed() }.count.formatted()) +// } label: { +// Text("Paires ayant confirmées") +// Text("Vous avez noté la confirmation de l'équipe") +// } +// .listRowView(color: .green) } Section { From 48e872a41bcfc42d8eb33b867b7fe24ed9b95060 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 26 Jun 2024 17:03:26 +0200 Subject: [PATCH 06/10] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- PadelClub/Data/Event.swift | 2 +- PadelClub/Data/Tournament.swift | 9 ++++++++ .../Navigation/Agenda/EventListView.swift | 18 ++++++++++++++++ .../Navigation/Toolbox/ToolboxView.swift | 21 ++++++++++++++++++- .../Components/TournamentStatusView.swift | 11 +++++----- .../Views/Tournament/TournamentInitView.swift | 4 ++-- 7 files changed, 58 insertions(+), 11 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 4ed8b55..51913ad 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1882,7 +1882,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1922,7 +1922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 679bdad..2bf385c 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -37,7 +37,7 @@ class Event: ModelObject, Storable { // MARK: - Computed dependencies var tournaments: [Tournament] { - DataStore.shared.tournaments.filter { $0.event == self.id } + DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false } } func clubObject() -> Club? { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index eee8c5f..70e79b8 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1258,6 +1258,15 @@ defer { return [tournamentLevel.localizedLabel(displayStyle) + " " + tournamentCategory.localizedLabel(), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ") } + func localizedTournamentType() -> String { + switch tournamentLevel { + case .unlisted: + return tournamentLevel.localizedLabel(.short) + default: + return tournamentLevel.localizedLabel(.short) + tournamentCategory.localizedLabel(.short) + } + } + func hideWeight() -> Bool { return tournamentLevel.hideWeight() } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index 2520186..e0575ae 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -110,6 +110,24 @@ struct EventListView: View { Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow") } } + #if DEBUG + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button(role: .destructive) { + do { + let event = tournament.eventObject() + let isLastTournament = event?.tournaments.count == 1 + try dataStore.tournaments.delete(instance: tournament) + if let event, isLastTournament { + try dataStore.events.delete(instance: event) + } + } catch { + Logger.error(error) + } + } label: { + LabelDelete() + } + } + #endif } private func _federalTournamentView(_ federalTournament: FederalTournament) -> some View { diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index 61811b5..85ddc43 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -11,7 +11,8 @@ import LeStorage struct ToolboxView: View { @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel - + @State private var didResetApiCalls: Bool = false + var body: some View { @Bindable var navigation = navigation NavigationStack(path: $navigation.toolboxPath) { @@ -19,6 +20,11 @@ struct ToolboxView: View { Section { Text("Version de l'application").badge(PadelClubApp.appVersion) + .onTapGesture(count: 5) { + Store.main.resetApiCalls() + didResetApiCalls = true + } + SupportButtonView(contentIsUnavailable: false) if Store.main.userId == "94f45ed2-8938-4c32-a4b6-e4525073dd33" { @@ -137,6 +143,19 @@ struct ToolboxView: View { Link("Accéder au guide de la compétition de la FFT", destination: URLs.padelRules.url) } } + .overlay(alignment: .bottom) { + if didResetApiCalls { + Label("failed api calls deleted", systemImage: "checkmark") + .toastFormatted() + .deferredRendering(for: .seconds(3)) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + didResetApiCalls = false + } + } + } + } + .navigationTitle(TabDestination.toolbox.title) } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift index e92c761..3b83c2b 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -46,7 +46,12 @@ struct TournamentStatusView: View { RowButtonView("Supprimer le tournoi", role: .destructive) { if tournament.payment == nil { do { + let event = tournament.eventObject() + let isLastTournament = event?.tournaments.count == 1 try dataStore.tournaments.delete(instance: tournament) + if let event, isLastTournament { + try dataStore.events.delete(instance: event) + } } catch { Logger.error(error) } @@ -62,10 +67,6 @@ struct TournamentStatusView: View { } navigation.path = NavigationPath() } - } footer: { - if tournament.payment == nil { - Text("") - } } if tournament.hasEnded() == false && tournament.isCanceled == false { @@ -81,7 +82,7 @@ struct TournamentStatusView: View { dismiss() } } footer: { - Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main.rawValue))")) + Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi reste visible sur [Padel Club](\(URLs.main.rawValue))")) } } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index f0de978..7c0d6fd 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -41,10 +41,10 @@ struct TournamentInitView: View { NavigationLink(value: Screen.settings) { LabeledContent { - Text(tournament.tournamentTitle(.short)) + Text(tournament.localizedTournamentType()) } label: { LabelSettings() - Text("Formats, statut, club, prix, etc.") + Text("Formats, club, prix et plus") } } From d4dc4422cafa635456907553d9860a4f3f9cb793 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 26 Jun 2024 20:15:44 +0200 Subject: [PATCH 07/10] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- PadelClub/Data/PlayerRegistration.swift | 11 ++++++++-- .../Views/Calling/SeedsCallingView.swift | 18 ++++++++--------- PadelClub/Views/Calling/SendToAllView.swift | 4 +++- .../GroupStageTeamReplacementView.swift | 12 ++++++----- .../Components/EditablePlayerView.swift | 4 ++-- .../Views/Tournament/FileImportView.swift | 20 ++++++++++++++----- .../Tournament/Screen/BroadcastView.swift | 2 +- .../Tournament/TournamentBuildView.swift | 2 +- 9 files changed, 49 insertions(+), 28 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 51913ad..fc7c7c7 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1882,7 +1882,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1922,7 +1922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index a4a6c1f..fbb07ca 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -80,8 +80,15 @@ class PlayerRegistration: ModelObject, Storable { licenceId = federalData[3] clubName = federalData[4] rank = Int(federalData[5]) - email = federalData[6] - phoneNumber = federalData[7] + let _email = federalData[6] + if _email.isEmpty == false { + self.email = _email + } + let _phoneNumber = federalData[7] + if _phoneNumber.isEmpty == false { + self.phoneNumber = _phoneNumber + } + source = .beachPadel if sexUnknown { if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index 8ab7110..d4716dd 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -9,7 +9,7 @@ import SwiftUI struct SeedsCallingView: View { @Environment(Tournament.self) var tournament: Tournament - @State private var displayByMatch: Bool = false + @State private var displayByMatch: Bool = true var body: some View { List { @@ -48,6 +48,14 @@ struct SeedsCallingView: View { } let keys = times.keys.compactMap { $0 }.sorted() List { + + Section { + RowButtonView(displayByMatch ? "Regrouper par horaire" : "Lister par match") { + displayByMatch.toggle() + } + } + + if displayByMatch == false { ForEach(keys, id: \.self) { time in if let matches = times[time] { @@ -109,14 +117,6 @@ struct SeedsCallingView: View { .navigationTitle(round.roundTitle()) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button(displayByMatch ? "par horaire" : "par match") { - displayByMatch.toggle() - } - .buttonBorderShape(.capsule) - } - } } } diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 2ee2938..0a449c6 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -20,7 +20,7 @@ struct SendToAllView: View { @State private var sentError: ContactManagerError? = nil let addLink: Bool // @State var cannotPayForTournament: Bool = false - @State private var pageLink: PageLink = .teams + @State private var pageLink: PageLink = .matches @State var showSubscriptionView: Bool = false @State var showUserCreationView: Bool = false @@ -75,6 +75,8 @@ struct SendToAllView: View { .tag(round.id) } } + } footer: { + Text("Si vous ne souhaitez pas contacter toutes les équipes, choisissez un ou plusieurs groupes d'équipes manuellement.") } if addLink { diff --git a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift index 5c1b8f2..6a89cd0 100644 --- a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift +++ b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift @@ -39,7 +39,7 @@ struct GroupStageTeamReplacementView: View { private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String { let left = teamRange.left?.weight let right = teamRange.right?.weight - let sides = [left, right].compactMap({ $0 }).map { $0 - _getWeight() } + let sides = [left, right].compactMap({ $0 }).map { max($0 - _getWeight(), 1) } return sides.map({ String($0) }).joined(separator: ",") } @@ -73,9 +73,11 @@ struct GroupStageTeamReplacementView: View { .labelsHidden() .pickerStyle(.inline) } header: { - Text("Remplacer") - } footer: { - Text("Remplacement de l'équipe ou d'un joueur particulier") + if let selectedPlayer { + Text("Remplacer \(selectedPlayer.playerLabel())") + } else { + Text("Remplacer toute l'équipe") + } } if let teamRange { @@ -147,7 +149,7 @@ struct GroupStageTeamReplacementView: View { @ViewBuilder var body: some View { - if let team { + if let team, team.weight + playerWeight > 0 { Text((team.weight + playerWeight).formatted()).font(.largeTitle) } else { Text("Aucune limite") diff --git a/PadelClub/Views/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index 4eb3b75..46c5ea3 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -96,8 +96,8 @@ struct EditablePlayerView: View { } if let mail = player.email, let url = URL(string: "mailto:\(mail)") { Link(destination: url) { - Label("SMS", systemImage: "message") - Text(mail) + Label("Mail", systemImage: "envelope") + Text(mail).lineLimit(1) } } diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 0641b19..df1ca0f 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -142,7 +142,7 @@ struct FileImportView: View { } } - if let tournaments = tournament.eventObject()?.tournaments, tournaments.count > 1, fileProvider == .frenchFederation { + if let event = tournament.eventObject(), event.tenupId != nil, event.tournaments.count > 1, fileProvider == .frenchFederation { Section { RowButtonView("Importer pour tous les tournois") { multiImport = true @@ -443,10 +443,20 @@ struct FileImportView: View { await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: rankSourceDate) } - let tournaments = tournament.eventObject()?.tournaments ?? [tournament] - for tournament in tournaments { - let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: tournament, fileProvider: fileProvider) - self.teams += _teams + let event: Event? = tournament.eventObject() + if let event, event.tenupId != nil { + var categoriesDone: [TournamentCategory] = [] + for someTournament in event.tournaments { + if categoriesDone.contains(someTournament.tournamentCategory) == false { + let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: someTournament, fileProvider: fileProvider) + self.teams += _teams + categoriesDone.append(someTournament.tournamentCategory) + } else { + errorMessage = "Attention, l'événement possède plusieurs tournois d'une même catégorie (homme, femme, mixte), Padel Club ne peut savoir quelle équipe appartient à quel tournoi." + } + } + } else { + self.teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: tournament, fileProvider: fileProvider) } await MainActor.run { diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 252be48..6ee3edc 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -23,7 +23,7 @@ struct BroadcastView: View { let filter = CIFilter.qrCodeGenerator() @State private var urlToShow: String? @State private var tvMode: Bool = false - @State private var pageLink: PageLink = .teams + @State private var pageLink: PageLink = .matches let createAccountTip = CreateAccountTip() let tournamentPublishingTip = TournamentPublishingTip() diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 9852f12..b443928 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -64,7 +64,7 @@ struct TournamentBuildView: View { } label: { Text("Tableau") if tournament.shouldVerifyBracket { - Text("Vérifier la tableau").foregroundStyle(.logoRed) + Text("Vérifier le tableau").foregroundStyle(.logoRed) } else if let description = bracketStatus?.1 { Text(description) } else if let range = bracketStatus?.2 { From 31ef90c4dd897bf2aaf741fb0cc378e8800b1bf3 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 27 Jun 2024 12:15:25 +0200 Subject: [PATCH 08/10] fix broadcast sections --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Utils/URLs.swift | 2 +- PadelClub/Views/Calling/SendToAllView.swift | 4 +- .../Tournament/Screen/BroadcastView.swift | 416 +++++++++--------- 4 files changed, 212 insertions(+), 214 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index fc7c7c7..d4e011c 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1882,7 +1882,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 74; + CURRENT_PROJECT_VERSION = 75; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1922,7 +1922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 74; + CURRENT_PROJECT_VERSION = 75; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index e6aa0de..5f00aee 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -28,7 +28,7 @@ enum PageLink: String, Identifiable, CaseIterable { case teams = "Équipes" case summons = "Convocations" case groupStages = "Poules" - case matches = "Matchs" + case matches = "Tournoi" case rankings = "Classement" case broadcast = "Mode TV (Tournoi)" case clubBroadcast = "Mode TV (Club)" diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 0a449c6..e5c43bb 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -84,10 +84,10 @@ struct SendToAllView: View { let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] Picker(selection: $pageLink) { ForEach(links) { pageLink in - Text(pageLink.localizedLabel()) + Text(pageLink.localizedLabel()).tag(pageLink) } } label: { - Text("Choisir une page du tournoi en particulier") + Text("Lien à partager") } .pickerStyle(.menu) } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 6ee3edc..4e62923 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -34,264 +34,245 @@ struct BroadcastView: View { List { if Store.main.userId == nil { Section { - TipView(createAccountTip) { action in - switch action.id { - case CreateAccountTip.ActionKey.accessPadelClubWebPage.rawValue: - UIApplication.shared.open(URLs.main.url) - case CreateAccountTip.ActionKey.createAccount.rawValue: + ContentUnavailableView { + Label("Créer votre compte Padel Club", systemImage: "person.bust") + + } description: { + let message = "Un compte est nécessaire pour publier le tournoi sur [Padel Club](\(URLs.main.rawValue)) et profiter de toutes les pages du site, comme le mode TV pour transformer l'expérience de vos tournois !" + Text(.init(message)) + + } actions: { + RowButtonView("Créer votre compte") { navigation.selectedTab = .umpire - default: - break - //todo -// case CreateAccountTip.ActionKey.learnMore.rawValue: -// UIApplication.shared.open(URLs.padelClubLandingPage.url) + } + + RowButtonView("Jeter un oeil au site Padel Club") { + UIApplication.shared.open(URLs.main.url) } } - .tipStyle(tint: .master) - } - } - - Section { - TipView(tournamentPublishingTip) { action in - UIApplication.shared.open(URLs.main.url) } - .tipStyle(tint: nil) - } - Section { - TipView(tournamentTVBroadcastTip) - .tipStyle(tint: nil) - } - - if tournament.isPrivate == false { - if let url = tournament.shareURL(.clubBroadcast) { - Section { - Link(destination: url) { - Text(url.absoluteString) - } - .contextMenu { - Button("Copier") { - let pasteboard = UIPasteboard.general - pasteboard.string = url.absoluteString - } - } - } header: { - Text("Lien pour la diffusion TV") - } footer: { - Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !") + } else { + Section { + TipView(tournamentPublishingTip) { action in + UIApplication.shared.open(URLs.main.url) } + .tipStyle(tint: nil) } - Section { - LabeledContent { - if tournament.isTournamentPublished() { - Image(systemName:"checkmark").foregroundStyle(.green) - } else { - Text(tournament.publishedTournamentDate().formatted()) + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()).tag(pageLink) } } label: { - if tournament.isTournamentPublished() { - Text("Publiée") - } else { - Text("Publication prévue") - } - } - - if tournament.canBePublished() == false { - Text("Pour être visible automatiquement, le tournoi doit avoir été créé il y a 24h, avoir une structure et au moins 4 inscriptions.") + Text("Choisir la page à partager") } + .pickerStyle(.menu) + actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink)) } header: { - Text("Information sur le tournoi") - } footer: { - if Date() < tournament.publishedTournamentDate() || tournament.canBePublished() == false { - HStack { - Spacer() - FooterButtonView(tournament.publishTournament ? "masquer sur le site" : "publier maintenant") { - tournament.publishTournament.toggle() - _save() - } - } - } + Text("Lien du tournoi à partager") } - + Section { - LabeledContent { - if tournament.areTeamsPublished() { - Image(systemName:"checkmark").foregroundStyle(.green) - } else { - Text(tournament.publishedTeamsDate().formatted()) - } - } label: { - if tournament.areTeamsPublished() { - Text("Publiée") - } else { - Text("Publication prévue") - } + 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") + } + + if tournament.isPrivate == false { + Section { + TipView(tournamentTVBroadcastTip) + .tipStyle(tint: nil) } - Toggle(isOn: $tournament.hideTeamsWeight) { - Text("Masquer les poids des équipes") - } - } header: { - Text("Liste des équipes") - } footer: { - if Date() < tournament.publishedTeamsDate() { - HStack { - Spacer() - FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") { - tournament.publishTeams.toggle() - _save() + if let url = tournament.shareURL(.clubBroadcast) { + Section { + Link(destination: url) { + Text(url.absoluteString) } + .contextMenu { + Button("Copier") { + let pasteboard = UIPasteboard.general + pasteboard.string = url.absoluteString + } + } + } header: { + Text("Lien pour la diffusion TV") + } footer: { + Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !") } } - } - - Section { - LabeledContent { - if tournament.areSummonsPublished() { - Image(systemName:"checkmark").foregroundStyle(.green) - } else { - Text(tournament.publishedTeamsDate().formatted()) + + + Section { + LabeledContent { + if tournament.isTournamentPublished() { + Image(systemName:"checkmark").foregroundStyle(.green) + } else { + Text(tournament.publishedTournamentDate().formatted()) + } + } label: { + if tournament.isTournamentPublished() { + Text("Publiée") + } else { + Text("Publication prévue") + } } - } label: { - if tournament.areSummonsPublished() { - Text("Publiées") - } else { - Text("Publication prévue") + + if tournament.canBePublished() == false { + Text("Pour être visible automatiquement, le tournoi doit avoir été créé il y a 24h, avoir une structure et au moins 4 inscriptions.") } - } - } header: { - Text("Convocations") - } footer: { - if Date() < tournament.publishedTeamsDate() { - HStack { - Spacer() - FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") { - tournament.publishSummons.toggle() - _save() + } header: { + Text("Information sur le tournoi") + } footer: { + if Date() < tournament.publishedTournamentDate() || tournament.canBePublished() == false { + HStack { + Spacer() + FooterButtonView(tournament.publishTournament ? "masquer sur le site" : "publier maintenant") { + tournament.publishTournament.toggle() + _save() + } } } } - } - - if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() { + Section { - let areGroupStagesPublished = tournament.areGroupStagesPublished() LabeledContent { - if areGroupStagesPublished { + if tournament.areTeamsPublished() { Image(systemName:"checkmark").foregroundStyle(.green) } else { - Text(publishedGroupStagesDate.formatted()) + Text(tournament.publishedTeamsDate().formatted()) } } label: { - if areGroupStagesPublished { - Text("Publiées") + if tournament.areTeamsPublished() { + Text("Publiée") } else { Text("Publication prévue") } } + + Toggle(isOn: $tournament.hideTeamsWeight) { + Text("Masquer les poids des équipes") + } } header: { - Text("Poules") + Text("Liste des équipes") } footer: { - if Date() < publishedGroupStagesDate { + if Date() < tournament.publishedTeamsDate() { HStack { Spacer() - FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") { - tournament.publishGroupStages.toggle() + FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") { + tournament.publishTeams.toggle() _save() } } } } - } - - if let publishedBracketsDate = tournament.publishedBracketsDate() { + Section { - let areBracketsPublished = tournament.areBracketsPublished() LabeledContent { - if areBracketsPublished { + if tournament.areSummonsPublished() { Image(systemName:"checkmark").foregroundStyle(.green) } else { - Text(publishedBracketsDate.formatted()) + Text(tournament.publishedTeamsDate().formatted()) } } label: { - if areBracketsPublished { - Text("Publié") + if tournament.areSummonsPublished() { + Text("Publiées") } else { Text("Publication prévue") } } } header: { - Text("Tableau") + Text("Convocations") } footer: { - if Date() < publishedBracketsDate { + if Date() < tournament.publishedTeamsDate() { HStack { Spacer() - FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") { - tournament.publishBrackets.toggle() + FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") { + tournament.publishSummons.toggle() _save() } } } } - } - } - - //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)) - } - - Section { - LabeledContent { - actionForURL(URLs.main.url) - } label: { - Text("Padel Club") - } - - let club = tournament.club() - LabeledContent { - if let clubURL = club?.shareURL() { - actionForURL(clubURL) - } - } label: { - Text("Club") - if let club { - Text(club.clubTitle()) - } else { - Text("Aucun club indiqué pour ce tournoi") + + if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() { + Section { + let areGroupStagesPublished = tournament.areGroupStagesPublished() + LabeledContent { + if areGroupStagesPublished { + Image(systemName:"checkmark").foregroundStyle(.green) + } else { + Text(publishedGroupStagesDate.formatted()) + } + } label: { + if areGroupStagesPublished { + Text("Publiées") + } else { + Text("Publication prévue") + } + } + } header: { + Text("Poules") + } footer: { + if Date() < publishedGroupStagesDate { + HStack { + Spacer() + FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") { + tournament.publishGroupStages.toggle() + _save() + } + } + } + } } - } - - if let url = tournament.shareURL(pageLink) { - LabeledContent { - actionForURL(url) - } label: { - Text("Tournoi") - Text(pageLink.localizedLabel()) + + if let publishedBracketsDate = tournament.publishedBracketsDate() { + Section { + let areBracketsPublished = tournament.areBracketsPublished() + LabeledContent { + if areBracketsPublished { + Image(systemName:"checkmark").foregroundStyle(.green) + } else { + Text(publishedBracketsDate.formatted()) + } + } label: { + if areBracketsPublished { + Text("Publié") + } else { + Text("Publication prévue") + } + } + } header: { + Text("Tableau") + } footer: { + if Date() < publishedBracketsDate { + HStack { + Spacer() + FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") { + tournament.publishBrackets.toggle() + _save() + } + } + } + } } } - let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] - Picker(selection: $pageLink) { - ForEach(links) { pageLink in - Text(pageLink.localizedLabel()).tag(pageLink) + //todo waitinglist & info + + Section { + Toggle(isOn: $tournament.isPrivate) { + Text("Tournoi privé") } - } label: { - Text("Modifier la page du tournoi à partager") + } footer: { + let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))" + Text(.init(footerString)) } - .pickerStyle(.menu) - } header: { - Text("Liens à partager") - .textCase(nil) } - } .headerProminence(.increased) .navigationTitle("Publication") @@ -343,31 +324,48 @@ struct BroadcastView: View { } @ViewBuilder - func actionForURL(_ url: URL, removeSource: Bool = false) -> some View { - Menu { - Button { - UIApplication.shared.open(url) + func actionForURL(title: String, description: String? = nil, url: URL?, removeSource: Bool = false) -> some View { + if let url { + Menu { + Button { + UIApplication.shared.open(url) + } label: { + Label("Voir", systemImage: "safari") + } + + Button { + urlToShow = url.absoluteString + } label: { + Label("QRCode", systemImage: "qrcode") + } + + ShareLink(item: url) { + Label("Partager le lien", systemImage: "link") + } } label: { - Label("Voir", systemImage: "safari") + LabeledContent { + Image(systemName: "square.and.arrow.up") + .foregroundColor(.master) + } label: { + Text(title) + .foregroundColor(.primary) + if let description { + Text(description) + .foregroundColor(.secondary) + } + } } - - Button { - urlToShow = url.absoluteString + .buttonStyle(.plain) + } else { + LabeledContent { + Image(systemName: "xmark").foregroundColor(.logoYellow) } label: { - Label("QRCode", systemImage: "qrcode") - } - - ShareLink(item: url) { - Label("Partager le lien", systemImage: "link") - } - } label: { - HStack { - Spacer() - Image(systemName: "square.and.arrow.up") + Text(title) + if let description { + Text(description) + } } } - .frame(maxWidth: .infinity) - .buttonStyle(.borderless) } From ac3ca1f6512ec7634a68e98603897a8a1df898a3 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 27 Jun 2024 15:57:21 +0200 Subject: [PATCH 09/10] b76 --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/Tournament.swift | 33 +++++++++++++--- .../GroupStage/GroupStageSettingsView.swift | 38 ++++++++++++++++++ PadelClub/Views/Round/RoundSettingsView.swift | 39 +++++++++++++++++++ PadelClub/Views/Round/RoundsView.swift | 2 +- .../Screen/InscriptionManagerView.swift | 28 ++++++++----- 6 files changed, 126 insertions(+), 18 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index d4e011c..7e3feab 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1882,7 +1882,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 75; + CURRENT_PROJECT_VERSION = 76; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1922,7 +1922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 75; + CURRENT_PROJECT_VERSION = 76; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 70e79b8..648ca9b 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -550,12 +550,7 @@ defer { } func groupStageSpots() -> Int { - let _groupStagesCount = groupStages().count - if groupStageCount != _groupStagesCount { - return groupStageCount * teamsPerGroupStage - } else { - return groupStages().map { $0.size }.reduce(0,+) - } + return groupStages().map { $0.size }.reduce(0,+) } func seeds() -> [TeamRegistration] { @@ -1853,6 +1848,32 @@ defer { } + typealias TeamPlacementIssue = (shouldBeInIt: [String], shouldNotBeInIt: [String]) + func groupStageTeamPlacementIssue() -> TeamPlacementIssue { + let selected = selectedSortedTeams() + let allTeams = unsortedTeams() + let newGroup = selected.suffix(groupStageSpots()) + let currentGroup = allTeams.filter({ $0.groupStagePosition != nil }) + let selectedIds = newGroup.map { $0.id } + let groupIds = currentGroup.map { $0.id } + let shouldBeInIt = Set(selectedIds).subtracting(groupIds) + let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds) + return (Array(shouldBeInIt), Array(shouldNotBeInIt)) + } + + func bracketTeamPlacementIssue() -> TeamPlacementIssue { + let selected = selectedSortedTeams() + let allTeams = unsortedTeams() + let seedCount = max(selected.count - groupStageSpots(), 0) + let newGroup = selected.prefix(seedCount) + let currentGroup = allTeams.filter({ $0.bracketPosition != nil }) + let selectedIds = newGroup.map { $0.id } + let groupIds = currentGroup.map { $0.id } + let shouldBeInIt = Set(selectedIds).subtracting(groupIds) + let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds) + return (Array(shouldBeInIt), Array(shouldNotBeInIt)) + } + // MARK: - func insertOnServer() throws { diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index bbff2e4..a2feafc 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -18,6 +18,44 @@ struct GroupStageSettingsView: View { if tournament.shouldVerifyGroupStage { Section { + let issues = tournament.groupStageTeamPlacementIssue() + DisclosureGroup { + ForEach(issues.shouldBeInIt, id: \.self) { id in + if let team: TeamRegistration = Store.main.findById(id) { + TeamRowView(team: team) + } + } + } label: { + LabeledContent { + Text(issues.shouldBeInIt.count.formatted()) + } label: { + Text("Équipes à mettre en poule") + } + } + DisclosureGroup { + ForEach(issues.shouldNotBeInIt, id: \.self) { id in + if let team = dataStore.teamRegistrations.findById(id) { + Menu { + Button("Retirer de sa poule") { + team.resetGroupeStagePosition() + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } + } label: { + TeamRowView(team: team) + } + } + } + } label: { + LabeledContent { + Text(issues.shouldNotBeInIt.count.formatted()) + } label: { + Text("Équipes à retirer des poules") + } + } RowButtonView("Valider les poules en l'état", role: .destructive) { tournament.shouldVerifyGroupStage = false do { diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 51b87dc..f9f20dd 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -17,6 +17,45 @@ struct RoundSettingsView: View { List { if tournament.shouldVerifyBracket { Section { + let issues = tournament.bracketTeamPlacementIssue() + DisclosureGroup { + ForEach(issues.shouldBeInIt, id: \.self) { id in + if let team: TeamRegistration = Store.main.findById(id) { + TeamRowView(team: team) + } + } + } label: { + LabeledContent { + Text(issues.shouldBeInIt.count.formatted()) + } label: { + Text("Équipes à mettre dans le tableau") + } + } + DisclosureGroup { + ForEach(issues.shouldNotBeInIt, id: \.self) { id in + if let team = dataStore.teamRegistrations.findById(id) { + Menu { + Button("Retirer du tableau") { + team.resetBracketPosition() + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } + } label: { + TeamRowView(team: team) + } + } + } + } label: { + LabeledContent { + Text(issues.shouldNotBeInIt.count.formatted()) + } label: { + Text("Équipes à retirer du tableau") + } + } + RowButtonView("Valider l'état du tableau", role: .destructive) { tournament.shouldVerifyBracket = false do { diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift index 913893e..db02f53 100644 --- a/PadelClub/Views/Round/RoundsView.swift +++ b/PadelClub/Views/Round/RoundsView.swift @@ -19,7 +19,7 @@ struct RoundsView: View { let _destinations = tournament.rounds().map { UpperRound(round: $0) } self.destinations = _destinations let availableSeeds = tournament.availableSeeds() - if tournament.shouldVerifyBracket && availableSeeds.isEmpty { + if tournament.shouldVerifyBracket { _selectedRound = State(wrappedValue: nil) } else { _selectedRound = State(wrappedValue: _destinations.first(where: { $0.id == tournament.getActiveRound()?.id })) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index ad5b8ca..2abf66d 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -56,7 +56,6 @@ struct InscriptionManagerView: View { @State private var showSubscriptionView: Bool = false @State private var registrationIssues: Int? = nil @State private var sortedTeams: [TeamRegistration] = [] - @State private var unfilteredTeams: [TeamRegistration] = [] @State private var walkoutTeams: [TeamRegistration] = [] @State private var unsortedTeamsWithoutWO: [TeamRegistration] = [] @State private var unsortedPlayers: [PlayerRegistration] = [] @@ -118,7 +117,6 @@ struct InscriptionManagerView: View { private func _clearScreen() { teamPaste = nil unsortedPlayers.removeAll() - unfilteredTeams.removeAll() walkoutTeams.removeAll() unsortedTeamsWithoutWO.removeAll() sortedTeams.removeAll() @@ -181,7 +179,7 @@ struct InscriptionManagerView: View { _managementView() if _isEditingTeam() { _buildingTeamView() - } else if unfilteredTeams.isEmpty { + } else if sortedTeams.isEmpty { _inscriptionTipsView() } else { _teamRegisteredView() @@ -326,6 +324,15 @@ struct InscriptionManagerView: View { UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament) .tint(.master) } + .onChange(of: filterMode) { + _prepareTeams() + } + .onChange(of: sortingMode) { + _prepareTeams() + } + .onChange(of: byDecreasingOrdering) { + _prepareTeams() + } .toolbar { if _isEditingTeam() { ToolbarItem(placement: .cancellationAction) { @@ -454,6 +461,9 @@ struct InscriptionManagerView: View { } #endif sortedTeams = tournament.sortedTeams() + } + + var filteredTeams: [TeamRegistration] { var teams = sortedTeams if filterMode == .walkOut { @@ -465,9 +475,9 @@ struct InscriptionManagerView: View { } if byDecreasingOrdering { - self.unfilteredTeams = teams.reversed() + return teams.reversed() } else { - self.unfilteredTeams = teams + return teams } } @@ -496,10 +506,10 @@ struct InscriptionManagerView: View { if presentSearch == false { _rankHandlerView() _relatedTips() - _informationView(count: unfilteredTeams.count) + _informationView() } - let teams = searchField.isEmpty ? unfilteredTeams : unfilteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) + let teams = searchField.isEmpty ? filteredTeams : filteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) if teams.isEmpty && searchField.isEmpty == false { ContentUnavailableView { @@ -710,7 +720,7 @@ struct InscriptionManagerView: View { } } - private func _informationView(count: Int) -> some View { + private func _informationView() -> some View { Section { LabeledContent { Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) @@ -872,7 +882,7 @@ struct InscriptionManagerView: View { private func _isDuplicate() -> Bool { let ids : [String?] = _currentSelectionIds() - if unfilteredTeams.anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) { + if sortedTeams.anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) { return true } return false From dade6f3118bf4b569e4d58a4badb48fc92b12232 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 27 Jun 2024 15:58:56 +0200 Subject: [PATCH 10/10] enable verify user --- PadelClub/Views/Calling/CallView.swift | 11 +++++------ .../Views/Calling/Components/MenuWarningView.swift | 11 +++++------ PadelClub/Views/Calling/SendToAllView.swift | 11 +++++------ PadelClub/Views/Match/MatchDetailView.swift | 11 +++++------ 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 6aae0fd..8df33dc 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -229,12 +229,11 @@ struct CallView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - handler() -// if Store.main.userId != nil { -// handler() -// } else { -// self.showUserCreationView = true -// } + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index 0f7376a..fb16e2a 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -142,12 +142,11 @@ struct MenuWarningView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - handler() -// if Store.main.userId != nil { -// handler() -// } else { -// self.showUserCreationView = true -// } + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index e5c43bb..deabbf9 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -231,12 +231,11 @@ struct SendToAllView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - handler() -// if Store.main.userId != nil { -// handler() -// } else { -// self.showUserCreationView = true -// } + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index bc6d00c..71758af 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -457,12 +457,11 @@ struct MatchDetailView: View { } fileprivate func _verifyUser(_ handler: () -> ()) { - handler() -// if Store.main.userId != nil { -// handler() -// } else { -// self.showUserCreationView = true -// } + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } } fileprivate func _payTournamentAndExecute(_ handler: () -> ()) {