From 4748e13bba9c4287e8c85f643566566f8569116e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Fri, 28 Jun 2024 20:33:44 +0200 Subject: [PATCH 1/8] fix small stuff --- PadelClub/Data/PlayerRegistration.swift | 16 ++++++++++++---- PadelClub/Data/TeamRegistration.swift | 6 +++++- PadelClub/Data/Tournament.swift | 4 ++-- PadelClub/Utils/FileImportManager.swift | 25 ++++++++++++++++--------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index fbb07ca..78f46d2 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -73,13 +73,21 @@ class PlayerRegistration: ModelObject, Storable { self.source = .frenchFederation } - internal init(federalData: [String], sex: Int, sexUnknown: Bool) { - lastName = federalData[0].trimmed.uppercased() - firstName = federalData[1].trimmed.capitalized + internal init?(federalData: [String], sex: Int, sexUnknown: Bool) { + let _lastName = federalData[0].trimmed.uppercased() + let _firstName = federalData[1].trimmed.capitalized + if _lastName.isEmpty && _firstName.isEmpty { return nil } + lastName = _lastName + firstName = _firstName birthdate = federalData[2] licenceId = federalData[3] clubName = federalData[4] - rank = Int(federalData[5]) + let stringRank = federalData[5] + if stringRank.isEmpty { + rank = nil + } else { + rank = Int(stringRank) + } let _email = federalData[6] if _email.isEmpty == false { self.email = _email diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index d6df314..a57cd16 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -201,8 +201,12 @@ class TeamRegistration: ModelObject, Storable { } func includes(players: [PlayerRegistration]) -> Bool { + let unsortedPlayers = unsortedPlayers() + guard players.count == unsortedPlayers.count else { return false } return players.allSatisfy { player in - includes(player: player) + unsortedPlayers.anySatisfy { _player in + _player.isSameAs(player) + } } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 648ca9b..07b9c72 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -476,7 +476,7 @@ class Tournament : ModelObject, Storable { } func courtUsed() -> [Int] { -#if DEBUG_TIME //DEBUGING TIME +#if DEBUG //DEBUGING TIME let start = Date() defer { let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) @@ -484,7 +484,7 @@ defer { } #endif - let runningMatches : [Match] = DataStore.shared.matches.filter({ $0.isRunning() }).filter({ $0.tournamentId() == self.id }) + let runningMatches : [Match] = DataStore.shared.matches.filter({ $0.tournamentId() == self.id && $0.isRunning() }) return Set(runningMatches.compactMap { $0.courtIndex }).sorted() } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index b27ee9c..9686e04 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -268,13 +268,17 @@ class FileImportManager { case .mix: return 1 } } - if tournamentCategory == tournament.tournamentCategory { + if tournamentCategory == tournament.tournamentCategory { let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) - playerOne.setComputedRank(in: tournament) + playerOne?.setComputedRank(in: tournament) let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown) - playerTwo.setComputedRank(in: tournament) - let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]), tournament: tournament) - results.append(team) + playerTwo?.setComputedRank(in: tournament) + + let players = [playerOne, playerTwo].compactMap({ $0 }) + if players.isEmpty == false { + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + results.append(team) + } } } } @@ -320,12 +324,15 @@ class FileImportManager { } let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) - playerOne.setComputedRank(in: tournament) + playerOne?.setComputedRank(in: tournament) let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown) - playerTwo.setComputedRank(in: tournament) + playerTwo?.setComputedRank(in: tournament) - let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]), tournament: tournament) - results.append(team) + let players = [playerOne, playerTwo].compactMap({ $0 }) + if players.isEmpty == false { + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + results.append(team) + } } } } From 905ac6a18e60959444fb2ac4cd706af08db58647 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Fri, 28 Jun 2024 20:37:01 +0200 Subject: [PATCH 2/8] fix wording --- .../Tournament/Screen/Components/InscriptionInfoView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index aaf6d49..8555e71 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -106,9 +106,9 @@ struct InscriptionInfoView: View { } .listRowView(color: .red) } header: { - Text("Équipes non sélectionnées") + Text("Équipes ne devant plus être sélectionnées") } footer: { - Text("Il s'agit des équipes déjà placé en poule ou tableau qui sont actuellement en attente à cause de l'arrivée d'une nouvelle équipe ou une modification de classement.") + Text("Il s'agit des équipes précédement placées en poule ou tableau mais qui sont finalement maintenant en attente suite à l'arrivée d'une nouvelle équipe plus forte ou une modification de classement.") } Section { From a0b6c41c863879ca2bc7f81db4b4074caabbecd8 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sat, 29 Jun 2024 18:26:47 +0200 Subject: [PATCH 3/8] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 14 ++- PadelClub/Data/AppSettings.swift | 2 +- PadelClub/Data/Club.swift | 2 +- PadelClub/Data/Court.swift | 2 +- PadelClub/Data/DateInterval.swift | 2 +- PadelClub/Data/Event.swift | 2 +- PadelClub/Data/GroupStage.swift | 2 +- PadelClub/Data/Match.swift | 2 +- PadelClub/Data/MatchScheduler.swift | 2 +- PadelClub/Data/MonthData.swift | 2 +- PadelClub/Data/PlayerRegistration.swift | 2 +- PadelClub/Data/Round.swift | 2 +- PadelClub/Data/TeamRegistration.swift | 2 +- PadelClub/Data/TeamScore.swift | 2 +- PadelClub/Data/Tournament.swift | 4 +- PadelClub/ViewModel/SeedInterval.swift | 12 ++- .../Views/Calling/GroupStageCallingView.swift | 2 +- .../GroupStage/GroupStageSettingsView.swift | 6 ++ .../LoserGroupStageSettingsView.swift | 85 +++++++++++++++++++ 19 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7e3feab..af99cbc 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */; }; FF11628C2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */; }; + FF135BF92C2FCB8300C9247A /* LoserGroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */; }; FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; @@ -405,6 +406,7 @@ FF1162862BD004AD000C4809 /* EditingTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditingTeamView.swift; sourceTree = ""; }; FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUpdateManagerView.swift; sourceTree = ""; }; FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserRoundStepScheduleEditorView.swift; sourceTree = ""; }; + FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserGroupStageSettingsView.swift; sourceTree = ""; }; FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = ""; }; FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = ""; }; FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = ""; }; @@ -1143,6 +1145,7 @@ FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */, + FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */, FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3922BE3625200C2E883 /* Shared */, ); @@ -1529,6 +1532,7 @@ FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, + FF135BF92C2FCB8300C9247A /* LoserGroupStageSettingsView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, @@ -1882,13 +1886,14 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 76; + CURRENT_PROJECT_VERSION = 77; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; ENABLE_MODULE_VERIFIER = YES; ENABLE_PREVIEWS = YES; + GCC_OPTIMIZATION_LEVEL = fast; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; @@ -1906,10 +1911,12 @@ ); MARKETING_VERSION = 0.1; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_SWIFT_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-Onone"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1922,7 +1929,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 76; + CURRENT_PROJECT_VERSION = 77; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1948,6 +1955,7 @@ 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)"; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/PadelClub/Data/AppSettings.swift b/PadelClub/Data/AppSettings.swift index 89a5763..6f08933 100644 --- a/PadelClub/Data/AppSettings.swift +++ b/PadelClub/Data/AppSettings.swift @@ -10,7 +10,7 @@ import LeStorage import SwiftUI @Observable -class AppSettings: MicroStorable { +final class AppSettings: MicroStorable { var lastDataSource: String? = nil var didCreateAccount: Bool = false diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index f18437c..eea0604 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -10,7 +10,7 @@ import SwiftUI import LeStorage @Observable -class Club : ModelObject, Storable, Hashable { +final class Club : ModelObject, Storable, Hashable { static func resourceName() -> String { return "clubs" } static func tokenExemptedMethods() -> [HTTPMethod] { return [.get] } diff --git a/PadelClub/Data/Court.swift b/PadelClub/Data/Court.swift index e68e50c..4ef3998 100644 --- a/PadelClub/Data/Court.swift +++ b/PadelClub/Data/Court.swift @@ -10,7 +10,7 @@ import SwiftUI import LeStorage @Observable -class Court : ModelObject, Storable, Hashable { +final class Court : ModelObject, Storable, Hashable { static func resourceName() -> String { return "courts" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/DateInterval.swift b/PadelClub/Data/DateInterval.swift index a82036f..5c2c5b6 100644 --- a/PadelClub/Data/DateInterval.swift +++ b/PadelClub/Data/DateInterval.swift @@ -10,7 +10,7 @@ import SwiftUI import LeStorage @Observable -class DateInterval: ModelObject, Storable { +final class DateInterval: ModelObject, Storable { static func resourceName() -> String { return "date-intervals" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 2bf385c..50dc9c2 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -10,7 +10,7 @@ import LeStorage import SwiftUI @Observable -class Event: ModelObject, Storable { +final class Event: ModelObject, Storable { static func resourceName() -> String { return "events" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index af6175a..570e32b 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -11,7 +11,7 @@ import Algorithms import SwiftUI @Observable -class GroupStage: ModelObject, Storable { +final class GroupStage: ModelObject, Storable { static func resourceName() -> String { "group-stages" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 26814b2..7af5681 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -9,7 +9,7 @@ import Foundation import LeStorage @Observable -class Match: ModelObject, Storable { +final class Match: ModelObject, Storable { static func resourceName() -> String { "matches" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 9bda2a8..60606e8 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -10,7 +10,7 @@ import LeStorage import SwiftUI @Observable -class MatchScheduler : ModelObject, Storable { +final class MatchScheduler : ModelObject, Storable { static func resourceName() -> String { return "match-scheduler" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/MonthData.swift b/PadelClub/Data/MonthData.swift index 8ad05c8..2dea761 100644 --- a/PadelClub/Data/MonthData.swift +++ b/PadelClub/Data/MonthData.swift @@ -10,7 +10,7 @@ import SwiftUI import LeStorage @Observable -class MonthData : ModelObject, Storable { +final class MonthData : ModelObject, Storable { static func resourceName() -> String { return "month-data" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 78f46d2..373292f 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -9,7 +9,7 @@ import Foundation import LeStorage @Observable -class PlayerRegistration: ModelObject, Storable { +final class PlayerRegistration: ModelObject, Storable { static func resourceName() -> String { "player-registrations" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 60a137f..942fdd9 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -10,7 +10,7 @@ import LeStorage import SwiftUI @Observable -class Round: ModelObject, Storable { +final class Round: ModelObject, Storable { static func resourceName() -> String { "rounds" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index a57cd16..e571013 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -9,7 +9,7 @@ import Foundation import LeStorage @Observable -class TeamRegistration: ModelObject, Storable { +final class TeamRegistration: ModelObject, Storable { static func resourceName() -> String { "team-registrations" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/TeamScore.swift b/PadelClub/Data/TeamScore.swift index 8d4a33c..0859b7c 100644 --- a/PadelClub/Data/TeamScore.swift +++ b/PadelClub/Data/TeamScore.swift @@ -9,7 +9,7 @@ import Foundation import LeStorage @Observable -class TeamScore: ModelObject, Storable { +final class TeamScore: ModelObject, Storable { static func resourceName() -> String { "team-scores" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 07b9c72..b2bcbd5 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -9,7 +9,7 @@ import Foundation import LeStorage @Observable -class Tournament : ModelObject, Storable { +final class Tournament : ModelObject, Storable { static func resourceName() -> String { "tournaments" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } @@ -1865,7 +1865,7 @@ defer { let selected = selectedSortedTeams() let allTeams = unsortedTeams() let seedCount = max(selected.count - groupStageSpots(), 0) - let newGroup = selected.prefix(seedCount) + let newGroup = selected.prefix(seedCount) + selected.filter({ $0.qualified }) let currentGroup = allTeams.filter({ $0.bracketPosition != nil }) let selectedIds = newGroup.map { $0.id } let groupIds = currentGroup.map { $0.id } diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index 91adb31..525e14e 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -37,9 +37,15 @@ struct SeedInterval: Hashable, Comparable { func chunks() -> [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] + if split%2 == 0 { + 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 { + let firstHalf = SeedInterval(first: first, last: first + split, reduce: reduce) + let secondHalf = SeedInterval(first: first + split + 1, last: last, reduce: reduce) + return [firstHalf, secondHalf] + } } else { return nil } diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 03d333b..0b95b62 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -85,7 +85,7 @@ struct GroupStageCallingView: View { } } .overlay { - if groupStage.startDate == nil { + if groupStage.startDate == nil && groupStage._matches().filter({ $0.startDate != nil }).isEmpty { ContentUnavailableView { Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") } description: { diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index a2feafc..b39d49b 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -77,6 +77,12 @@ struct GroupStageSettingsView: View { } #endif +// NavigationLink { +// LoserGroupStageSettingsView(tournament: tournament) +// } label: { +// Text("Match de perdant de poules") +// } + if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty == false { Section { menuBuildAllGroupStages diff --git a/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift b/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift new file mode 100644 index 0000000..78ccdd0 --- /dev/null +++ b/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift @@ -0,0 +1,85 @@ +// +// LoserGroupStageSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 29/06/2024. +// + +import SwiftUI + +extension Round { + var isGroupStageLoserBracket: Bool { + return false + } +} + +extension Tournament { + func groupStageLoserBrackets() -> [Round] { + [] + } + + func removeGroupStageLoserBrackets() { + + } +} + +struct LoserGroupStageSettingsView: View { + var tournament: Tournament + @State private var loserGroupStageBracketType: Int? = nil + @State private var losers : Set = Set() + @Environment(\.editMode) private var editMode + + var body: some View { + List(selection: $losers) { + if tournament.groupStageLoserBrackets().isEmpty == false { + //for each all rounds without parent and loserGroupStage, ability to delete them + Section { + RowButtonView("Effacer", role: .destructive) { + tournament.removeGroupStageLoserBrackets() + } + } + } + + if self.editMode?.wrappedValue == .active { + Section { + //rajouter + toolbar valider / cancel + ForEach(tournament.groupStageTeams().filter({ $0.qualified == false })) { team in + TeamRowView(team: team).tag(team) + } + } header: { + Text("Sélection des perdants de poules") + } + } else { + Section { + RowButtonView("Ajouter un match de perdant") { + self.editMode?.wrappedValue = .active + } + } footer: { + Text("Permet d'ajouter un match de perdant de poules.") + } + } + } + .toolbar { + if self.editMode?.wrappedValue == .active { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler") { + self.editMode?.wrappedValue = .inactive + } + } + + ToolbarItem(placement: .topBarTrailing) { + Button("Valider") { + self.editMode?.wrappedValue = .inactive + //tournament.createGroupStageLoserBracket() + } + } + } + } + .navigationTitle("Match de perdant de poules") + .navigationBarBackButtonHidden(self.editMode?.wrappedValue == .active) + .navigationBarTitleDisplayMode(.inline) + .toolbar(.visible, for: .navigationBar) + .headerProminence(.increased) + .toolbarBackground(.visible, for: .navigationBar) + } +} From 9979824072c64ad1ff7a2c50abf65bc4d61c5ef9 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 30 Jun 2024 13:07:14 +0200 Subject: [PATCH 4/8] wip html pdf landscape refresh loser bracket match name update when ending edition --- PadelClub/Utils/HtmlGenerator.swift | 36 ++++++++++++----- PadelClub/Utils/HtmlService.swift | 40 ++++++++++++++----- PadelClub/Views/Round/LoserRoundView.swift | 13 +++++- .../Tournament/Screen/PrintSettingsView.swift | 36 ++++++++++++++--- 4 files changed, 98 insertions(+), 27 deletions(-) diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index 29682f1..af72534 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -31,10 +31,19 @@ class HtmlGenerator: ObservableObject { @Published var height: CGFloat = 0 private var webView: WKWebView = WKWebView() private var groupStageDone: Int = 0 + @Published var landscape: Bool = false + var baseWidth: CGFloat { + landscape ? 842 : 595 + } + + var baseHeight: CGFloat { + landscape ? 595 : 842 + } + var estimatedPageCount: Int { if let zoomLevel { - let pageSize = CGSize(width: 595 * (1 + zoomLevel), height: 812 * (1 + zoomLevel)) + let pageSize = CGSize(width: baseWidth * (1 + zoomLevel), height: baseHeight * (1 + zoomLevel)) let numberOfPageInWidth = Int(width / pageSize.width) + 1 let numberOfPageInHeight = Int(height / pageSize.height) + 1 return numberOfPageInWidth * numberOfPageInHeight @@ -50,17 +59,19 @@ class HtmlGenerator: ObservableObject { func generateWebView(webView: WKWebView) { self.webView = webView self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in + print("evaluateJavaScript", "readystage", complete, error) if complete != nil { self.webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in + print("evaluateJavaScript", "height", height, error) self.height = height as! CGFloat + self.webView.evaluateJavaScript("document.documentElement.scrollWidth", completionHandler: { (width, error) in + print("evaluateJavaScript", "width", width, error) + self.width = width as! CGFloat + if self.completionHandler != nil { + self.buildPDF() + } + }) }) - self.webView.evaluateJavaScript("document.documentElement.scrollWidth", completionHandler: { (width, error) in - self.width = width as! CGFloat - }) - } - - if self.completionHandler != nil { - self.buildPDF() } }) } @@ -75,7 +86,7 @@ class HtmlGenerator: ObservableObject { print("bracket", width, height) let config = WKPDFConfiguration() - config.rect = CGRect(origin: .zero, size: CGSize(width: Int(width), height: Int(width))) + config.rect = CGRect(origin: .zero, size: CGSize(width: Int(width), height: Int(height))) webView.createPDF(configuration: config){ result in switch result{ case .success(let data): @@ -111,7 +122,7 @@ class HtmlGenerator: ObservableObject { try? FileManager.default.removeItem(at: pdfURL!) print("buildPDF", width, height, zoomLevel ?? 0) if let zoomLevel { - let pageSize = CGSize(width: 595 * (1 + zoomLevel), height: 812 * (1 + zoomLevel)) + let pageSize = CGSize(width: baseWidth * (1 + zoomLevel), height: baseHeight * (1 + zoomLevel)) let numberOfPageInWidth = Int(width / pageSize.width) + 1 let numberOfPageInHeight = Int(height / pageSize.height) + 1 for w in 0.. String { + //HtmlService.groupstage(bracket: tournament.orderedBrackets.first!).html() + HtmlService.loserBracket(upperRound: upperRound).html(headName: displayHeads, withRank: displayRank, withScore: false) + } + var pdfURL: URL? { guard let pdfFolderURL = getFilePath() else { return nil diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index c083d34..33145b5 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -10,7 +10,8 @@ import Foundation enum HtmlService { case template(tournament: Tournament) - case bracket(tournament: Tournament, roundIndex: Int) + case bracket(round: Round) + case loserBracket(upperRound: Round) case match(match: Match) case player(entrant: TeamRegistration) case hiddenPlayer @@ -26,7 +27,7 @@ enum HtmlService { var fileName: String { switch self { - case .template: + case .template, .loserBracket: return "tournament-template" case .bracket: return "bracket-template" @@ -191,23 +192,42 @@ enum HtmlService { } template = template.replacingOccurrences(of: "{{matchDescription}}", with: "") return template - case .bracket(let tournament, let roundIndex): + case .bracket(let round): var template = "" var bracket = "" - if let round = tournament.rounds().first(where: { $0.index == roundIndex }) { - for (_, match) in round._matches().enumerated() { - template = template.appending(HtmlService.match(match: match).html(headName: headName, withRank: withRank, withScore: withScore)) - } - bracket = html.replacingOccurrences(of: "{{match-template}}", with: template) - bracket = bracket.replacingOccurrences(of: "{{roundLabel}}", with: round.roundTitle()) + for (_, match) in round._matches().enumerated() { + template = template.appending(HtmlService.match(match: match).html(headName: headName, withRank: withRank, withScore: withScore)) } + bracket = html.replacingOccurrences(of: "{{match-template}}", with: template) + bracket = bracket.replacingOccurrences(of: "{{roundLabel}}", with: round.roundTitle()) return bracket + case .loserBracket(let upperRound): + var template = html + template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: upperRound.correspondingLoserRoundTitle()) + var brackets = "" + for round in upperRound.loserRounds() { + brackets = brackets.appending(HtmlService.bracket(round: round).html(headName: headName, withRank: withRank, withScore: withScore)) + } + var winnerName = "" + let winner = """ +
    +
  •  
  • +
  • \(winnerName)
  • +
  •  
  • +
+ + """ + brackets = brackets.appending(winner) + + template = template.replacingOccurrences(of: "{{brackets}}", with: brackets) + return template case .template(let tournament): var template = html template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle(.short)) var brackets = "" for round in tournament.rounds() { - brackets = brackets.appending(HtmlService.bracket(tournament: tournament, roundIndex: round.index).html(headName: headName, withRank: withRank, withScore: withScore)) + brackets = brackets.appending(HtmlService.bracket(round: round).html(headName: headName, withRank: withRank, withScore: withScore)) } var winnerName = "" diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index 08f8205..fe3ea15 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct LoserRoundView: View { @EnvironmentObject var dataStore: DataStore @@ -22,7 +23,7 @@ struct LoserRoundView: View { print("func _roundDisabled", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) } #endif - return loserBracket.allMatches.allSatisfy({ $0.disabled == false }) + return loserBracket.allMatches.allSatisfy({ $0.disabled == true }) } private func _matches(loserRoundId: String?) -> [Match] { @@ -87,6 +88,16 @@ struct LoserRoundView: View { ToolbarItem(placement: .topBarTrailing) { Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { isEditingTournamentSeed.wrappedValue.toggle() + + if isEditingTournamentSeed.wrappedValue == false { + let allRoundMatches = loserBracket.allMatches + allRoundMatches.forEach({ $0.name = $0.roundTitle() }) + do { + try DataStore.shared.matches.addOrUpdate(contentOfs: allRoundMatches) + } catch { + Logger.error(error) + } + } } } } diff --git a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift index ddb8701..a8cc7d3 100644 --- a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift @@ -28,7 +28,6 @@ struct PrintSettingsView: View { // Toggle(isOn: $generator.displayHeads, label: { // Text("Afficher les têtes de séries") // }) - Toggle(isOn: $generator.displayRank, label: { Text("Afficher le classement du joueur") }) @@ -57,6 +56,17 @@ struct PrintSettingsView: View { } label: { Text("Zoom") } + .onChange(of: generator.zoomLevel) { + if generator.zoomLevel == nil { + generator.landscape = false + } + } + + if generator.zoomLevel != nil { + Toggle(isOn: $generator.landscape, label: { + Text("Format paysage") + }) + } HStack { Text("Nombre de page A4 à imprimer") @@ -106,11 +116,23 @@ struct PrintSettingsView: View { Section { NavigationLink { - WebViewPreview(bracket: true) + WebViewPreview() .environmentObject(generator) } label: { Text("Aperçu du tableau") } +// +// ForEach(tournament.rounds()) { round in +// if round.index > 0 { +// NavigationLink { +// WebViewPreview(round: round) +// .environmentObject(generator) +// } label: { +// Text("Aperçu \(round.correspondingLoserRoundTitle())") +// } +// } +// } +// ForEach(tournament.groupStages()) { groupStage in NavigationLink { WebViewPreview(groupStage: groupStage) @@ -260,13 +282,13 @@ struct WebView: UIViewRepresentable { struct WebViewPreview: View { @EnvironmentObject var generator: HtmlGenerator - let bracket: Bool let groupStage: GroupStage? - + let round: Round? + @State private var html: String? - init(bracket: Bool = false, groupStage: GroupStage? = nil) { - self.bracket = bracket + init(groupStage: GroupStage? = nil, round: Round? = nil) { + self.round = round self.groupStage = groupStage } @@ -280,6 +302,8 @@ struct WebViewPreview: View { .onAppear { if let groupStage { html = HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false) + } else if let round { + html = generator.generateLoserBracketHtml(upperRound: round) } else { html = generator.generateHtml() } From a6679088df2140614bb0b4fe1b10ca3667f7a184 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 30 Jun 2024 13:07:44 +0200 Subject: [PATCH 5/8] b78 --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index af99cbc..3655150 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1886,7 +1886,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 77; + CURRENT_PROJECT_VERSION = 78; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1929,7 +1929,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 77; + CURRENT_PROJECT_VERSION = 78; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; From f9614981217513ab9988e7db1df324b4de06bf60 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 1 Jul 2024 16:06:32 +0200 Subject: [PATCH 6/8] fix issues --- PadelClub/Data/Match.swift | 40 +- PadelClub/Data/Round.swift | 23 ++ PadelClub/Data/Tournament.swift | 82 ++-- PadelClub/Utils/URLs.swift | 1 + PadelClub/Views/Club/ClubSearchView.swift | 27 +- PadelClub/Views/Match/MatchSetupView.swift | 4 +- .../Navigation/Agenda/ActivityView.swift | 2 +- PadelClub/Views/Navigation/MainView.swift | 1 - .../CourtAvailabilitySettingsView.swift | 2 +- PadelClub/Views/Round/LoserRoundsView.swift | 11 +- PadelClub/Views/Team/TeamPickerView.swift | 28 +- .../Screen/InscriptionManagerView.swift | 2 +- .../Screen/TournamentRankView.swift | 379 +++++++++--------- .../Tournament/TournamentBuildView.swift | 7 +- PadelClubTests/ServerDataTests.swift | 3 +- 15 files changed, 348 insertions(+), 264 deletions(-) diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 7af5681..e245548 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -208,25 +208,10 @@ defer { func teamWillBeWalkOut(_ team: TeamRegistration) { resetMatch() - let previousScores = teamScores.filter({ $0.luckyLoser != nil }) + let existingTeamScore = teamScore(ofTeam: team) ?? TeamScore(match: id, team: team) + existingTeamScore.walkOut = 1 do { - try DataStore.shared.teamScores.delete(contentOfs: previousScores) - } catch { - Logger.error(error) - } - - if let existingTeamScore = teamScore(ofTeam: team) { - do { - try DataStore.shared.teamScores.delete(instance: existingTeamScore) - } catch { - Logger.error(error) - } - } - - let teamScoreWalkout = TeamScore(match: id, team: team) - teamScoreWalkout.walkOut = 1 - do { - try DataStore.shared.teamScores.addOrUpdate(instance: teamScoreWalkout) + try DataStore.shared.teamScores.addOrUpdate(instance: existingTeamScore) } catch { Logger.error(error) } @@ -242,24 +227,17 @@ defer { func setLuckyLoser(team: TeamRegistration, teamPosition: TeamPosition) { resetMatch() - let previousScores = teamScores.filter({ $0.luckyLoser != nil }) + let matchIndex = index + let position = matchIndex * 2 + teamPosition.rawValue + + let previousScores = teamScores.filter({ $0.luckyLoser == position }) do { try DataStore.shared.teamScores.delete(contentOfs: previousScores) } catch { Logger.error(error) } - - if let existingTeamScore = teamScore(ofTeam: team) { - do { - try DataStore.shared.teamScores.delete(instance: existingTeamScore) - } catch { - Logger.error(error) - } - } - - let matchIndex = index - let position = matchIndex * 2 + teamPosition.rawValue - let teamScoreLuckyLoser = TeamScore(match: id, team: team) + + let teamScoreLuckyLoser = teamScore(ofTeam: team) ?? TeamScore(match: id, team: team) teamScoreLuckyLoser.luckyLoser = position do { try DataStore.shared.teamScores.addOrUpdate(instance: teamScoreLuckyLoser) diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 942fdd9..3707917 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -556,6 +556,29 @@ defer { loserRounds().forEach { round in round.buildLoserBracket() } + + /* + return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat) + } + + do { + try DataStore.shared.matches.addOrUpdate(contentOfs: matches) + } catch { + Logger.error(error) + } + + matches.forEach { + $0.name = $0.roundObject?.roundTitle() + } + + do { + try DataStore.shared.matches.addOrUpdate(contentOfs: matches) + } catch { + Logger.error(error) + } + + + */ } var parentRound: Round? { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index b2bcbd5..cbf4b03 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -53,7 +53,8 @@ final class Tournament : ModelObject, Storable { var hideTeamsWeight: Bool = false var publishTournament: Bool = false var hidePointsEarned: Bool = false - + var publishRankings: Bool = false + @ObservationIgnored var navigationPath: [Screen] = [] @@ -100,9 +101,10 @@ final class Tournament : ModelObject, Storable { case _hideTeamsWeight = "hideTeamsWeight" case _publishTournament = "publishTournament" case _hidePointsEarned = "hidePointsEarned" + case _publishRankings = "publishRankings" } - internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false) { + internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false) { self.event = event self.name = name self.startDate = startDate @@ -139,6 +141,7 @@ final class Tournament : ModelObject, Storable { self.hideTeamsWeight = hideTeamsWeight self.publishTournament = publishTournament self.hidePointsEarned = hidePointsEarned + self.publishRankings = publishRankings } required init(from decoder: Decoder) throws { @@ -182,6 +185,7 @@ final class Tournament : ModelObject, Storable { hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false + publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false } fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() @@ -296,6 +300,7 @@ final class Tournament : ModelObject, Storable { try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight) try container.encode(publishTournament, forKey: ._publishTournament) try container.encode(hidePointsEarned, forKey: ._hidePointsEarned) + try container.encode(publishRankings, forKey: ._publishRankings) } fileprivate func _encodePayment(container: inout KeyedEncodingContainer) throws { @@ -1091,6 +1096,17 @@ defer { return selected.sorted(by: \.finalRanking!, order: .ascending) } + private func _removeStrings(from dictionary: inout [Int: [String]], stringsToRemove: [String]) { + for key in dictionary.keys { + if var stringArray = dictionary[key] { + // Remove all instances of each string in stringsToRemove + stringArray.removeAll { stringsToRemove.contains($0) } + dictionary[key] = stringArray + } + } + } + + func finalRanking() async -> [Int: [String]] { var teams: [Int: [String]] = [:] var ids: Set = Set() @@ -1106,6 +1122,14 @@ defer { } let others: [Round] = rounds.flatMap { round in + let losers = round.losers() + let minimumFinalPosition = round.seedInterval()?.last ?? teamCount + if teams[minimumFinalPosition] == nil { + teams[minimumFinalPosition] = losers.map { $0.id } + } else { + teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id }) + } + print("round", round.roundTitle()) let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false } print(rounds.count, rounds.map { $0.roundTitle() }) @@ -1124,28 +1148,40 @@ defer { print("losers", losers.count) if winners.isEmpty { let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) - 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) } + if disabledIds.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: 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.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.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) } + if winners.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: 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) } + } + + if losers.isEmpty == false { + _removeStrings(from: &teams, stringsToRemove: 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/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 5f00aee..dd36e02 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -14,6 +14,7 @@ enum URLs: String, Identifiable { case api = "https://xlr.alwaysdata.net/roads/" case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" //case padelClub = "https://padelclub.app" + case tenup = "https://tenup.fft.fr" case padelRules = "https://fft-site.cdn.prismic.io/fft-site/ZgLn3McYqOFdyF7n_LEGUIDEDELACOMPETITIONDEPADEL-MAJDECEMBRE2023.pdf" var id: String { return self.rawValue } diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index beea497..230feb9 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -27,7 +27,8 @@ struct ClubSearchView: View { @State private var searchPresented: Bool = false @State private var showingSettingsAlert = false @State private var newClub: Club? - + @State private var error: Error? + var presentClubCreationView: Binding { Binding( get: { newClub != nil }, set: { isPresented in @@ -59,7 +60,7 @@ struct ClubSearchView: View { searching = false searchAttempted = true } - + error = nil clubMarkers = [] guard let city = locationManager.city else { return } let response = try await NetworkFederalService.shared.federalClubs(city: city, radius: radius, location: locationManager.location) @@ -70,6 +71,8 @@ struct ClubSearchView: View { } } catch { print("getclubs", error) + self.error = error + Logger.error(error) } } @@ -143,12 +146,20 @@ struct ClubSearchView: View { } else if clubMarkers.isEmpty && searching == false && searchPresented == false { ContentUnavailableView { if searchAttempted { - Label("Aucun club trouvé", systemImage: "mappin.slash") + if error != nil { + Label("Une erreur est survenue", systemImage: "exclamationmark.circle.fill") + } else { + Label("Aucun club trouvé", systemImage: "mappin.slash") + } } else { Label("Recherche de club", systemImage: "location.circle") } } description: { - Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.") + if searchAttempted && error != nil { + Text("Tenup est peut-être en maintenance, veuillez ré-essayer plus tard.") + } else { + Text("Padel Club recherche via Tenup un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.") + } } actions: { if locationManager.manager.authorizationStatus != .restricted { RowButtonView("Chercher autour de moi") { @@ -161,6 +172,13 @@ struct ClubSearchView: View { } } } + + if error != nil { + Link(destination: URLs.tenup.url) { + Text("Voir si tenup est en maintenance") + } + } + RowButtonView("Chercher une ville ou un code postal") { searchPresented = true } @@ -343,6 +361,7 @@ struct ClubSearchView: View { private func _resetSearch() { searchAttempted = false + error = nil debouncableViewModel.debouncableText = "" searchedCity = "" locationManager.city = nil diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 08071b8..f983ad4 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -73,14 +73,14 @@ struct MatchSetupView: View { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] TeamPickerView(groupStagePosition: nil, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) - if walkOutSpot { + if walkOutSpot || team.bracketPosition != nil { match.setLuckyLoser(team: team, teamPosition: teamPosition) do { try dataStore.matches.addOrUpdate(instance: match) } catch { Logger.error(error) } - } else { + } else if team.bracketPosition == nil { team.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { try dataStore.matches.addOrUpdate(instance: match) diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 024c4b6..3983879 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -102,7 +102,7 @@ struct ActivityView: View { .overlay { if let error, navigation.agendaDestination == .tenup { ContentUnavailableView { - Label("Erreur", systemImage: "exclamationmark") + Label("Une erreur est survenue", systemImage: "exclamationmark.circle.fill") } description: { Text(error.localizedDescription) } actions: { diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index f7db5b3..d2176ca 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -91,7 +91,6 @@ struct MainView: View { } .environmentObject(dataStore) .task { - await self._checkSourceFileAvailability() if Store.main.hasToken() { do { try await dataStore.clubs.loadDataFromServerIfAllowed() diff --git a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift index 10733b2..37680cb 100644 --- a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift +++ b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift @@ -128,7 +128,7 @@ struct CourtAvailabilitySettingsView: View { .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Créneau indisponible") - .popover(isPresented: $showingPopover) { + .sheet(isPresented: $showingPopover) { NavigationStack { Form { Section { diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index 948d2ea..453dff3 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -177,13 +177,12 @@ struct LoserRoundsView: View { var body: some View { VStack(spacing: 0) { - GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: false) - if let selectedRound { + GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: true) + switch selectedRound { + case .some(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.")) - } + default: + LoserRoundSettingsView() } } .environment(\.isEditingTournamentSeed, $isEditingTournamentSeed) diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index 8b7b578..445a1b6 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -11,12 +11,20 @@ struct TeamPickerView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @Environment(\.dismiss) private var dismiss + @State private var confirmTeam: TeamRegistration? @State private var presentTeamPickerView: Bool = false @State private var searchField: String = "" var groupStagePosition: Int? = nil var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) - + + var confirmationRequest: Binding { + Binding { + confirmTeam != nil + } set: { _ in + } + } + var body: some View { Button { presentTeamPickerView = true @@ -86,12 +94,30 @@ struct TeamPickerView: View { Button { teamPicked(team) presentTeamPickerView = false +// if team.inRound() { +// confirmTeam = team +// } else { +// teamPicked(team) +// presentTeamPickerView = false +// } } label: { TeamRowView(team: team) .contentShape(Rectangle()) } .frame(maxWidth: .infinity) .buttonStyle(.plain) +// .confirmationDialog("Attention", isPresented: confirmationRequest, titleVisibility: .visible) { +// Button("Retirer du tableau", role: .destructive) { +// teamPicked(confirmTeam!) +// presentTeamPickerView = false +// } +// +// Button("Annuler", role: .cancel) { +// confirmTeam = nil +// } +// } message: { +// Text("Vous êtes sur le point de retirer cette équipe du tableau pour le replacer, cela effacera les résultats des matchs déjà joués par cette équipe dans le tableau.") +// } } } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 2abf66d..5c7866d 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -524,7 +524,7 @@ struct InscriptionManagerView: View { RowButtonView("Créer une équipe") { Task { - await MainActor.run() { + await MainActor.run { fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption()) fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] pasteString = searchField diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 08c655b..f47bb12 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -11,7 +11,8 @@ import LeStorage struct TournamentRankView: View { @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore - + @Environment(\.editMode) private var editMode + @State private var rankings: [Int: [TeamRegistration]] = [:] @State private var calculating = false @State private var selectedTeam: TeamRegistration? @@ -28,97 +29,71 @@ struct TournamentRankView: View { var body: some View { List { @Bindable var tournament = tournament - let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil }) - Section { - LabeledContent { - if let matchesLeft { - Text(matchesLeft.count.formatted()) - } else { - ProgressView() + let rankingsCalculated = tournament.selectedSortedTeams().anySatisfy({ $0.finalRanking != nil }) + if editMode?.wrappedValue.isEditing == false { + Section { + MatchListView(section: "Matchs restant", matches: matchesLeft, hideWhenEmpty: false, isExpanded: false) + MatchListView(section: "Matchs en cours", matches: runningMatches, hideWhenEmpty: false, isExpanded: false) + + Toggle(isOn: $tournament.hidePointsEarned) { + Text("Masquer les points gagnés") } - } label: { - Text("Matchs restant") - } - - LabeledContent { - if let runningMatches { - Text(runningMatches.count.formatted()) - } else { - ProgressView() + .onChange(of: tournament.hidePointsEarned) { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } } - } label: { - Text("Matchs en cours") - } - - LabeledContent { - if rankingPublished { - Image(systemName: "checkmark") - .foregroundStyle(.green) - } else { - Image(systemName: "xmark") - .foregroundStyle(.logoRed) + + Toggle(isOn: $tournament.publishRankings) { + Text("Publier sur Padel Club") + if let url = tournament.shareURL(.rankings) { + Link(destination: url) { + Text("Accéder à la page") + } + } } - } label: { - Text("Classement publié") - } - - Toggle(isOn: $tournament.hidePointsEarned) { - Text("Masquer les points gagnés") - } - .onChange(of: tournament.hidePointsEarned) { - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) + .onChange(of: tournament.publishRankings) { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } } } + } - if rankingPublished == false { - RowButtonView("Publier le classement", role: .destructive) { - _publishRankings() + if (editMode?.wrappedValue.isEditing == true || rankingsCalculated == false) && calculating == false { + Section { + RowButtonView(rankingsCalculated ? "Re-calculer le classement" : "Calculer", role: .destructive) { + await _calculateRankings() } - } else { - RowButtonView("Re-publier le classement", role: .destructive) { - _publishRankings() + } footer: { + if rankingsCalculated { + Text("Vos éditions seront perdus.") } } - } - - if rankingPublished { - Section { - RowButtonView("Supprimer le classement", role: .destructive) { - tournament.unsortedTeams().forEach { team in - team.finalRanking = nil - team.pointsEarned = nil + + if rankingsCalculated { + Section { + RowButtonView("Supprimer le classement", role: .destructive) { + tournament.unsortedTeams().forEach { team in + team.finalRanking = nil + team.pointsEarned = nil + } + _save() } - _save() } - } footer: { - Text(.init("Masque également le classement sur le site [Padel Club](\(URLs.main.rawValue))")) } } + - if rankingPublished { + let teamsRanked = tournament.teamsRanked() + if calculating == false && rankingsCalculated && teamsRanked.isEmpty == false { Section { - ForEach(tournament.teamsRanked()) { team in - let key = team.finalRanking ?? 0 - Button { - selectedTeam = team - } label: { - TeamRankCellView(team: team, key: key) - .frame(maxWidth: .infinity) - } - .contentShape(Rectangle()) - .buttonStyle(.plain) - } - } footer: { - Text("Vous pouvez appuyer sur une ligne pour éditer manuellement le classement calculé par Padel Club.") - } - } else { - let keys = rankings.keys.sorted() - ForEach(keys, id: \.self) { key in - if let rankedTeams = rankings[key] { - ForEach(rankedTeams) { team in + ForEach(teamsRanked) { team in + if let key = team.finalRanking { TeamRankCellView(team: team, key: key) } } @@ -130,42 +105,16 @@ struct TournamentRankView: View { self.runningMatches = await tournament.asyncRunningMatches(all) self.matchesLeft = await tournament.readyMatches(all) } - .alert("Position", isPresented: isEditingTeam) { - if let selectedTeam { - @Bindable var team = selectedTeam - TextField("Position", value: $team.finalRanking, format: .number) - .keyboardType(.numberPad) - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity) - - Button("Valider") { - selectedTeam.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: selectedTeam.finalRanking! - 1, count: tournament.teamCount) - do { - try dataStore.teamRegistrations.addOrUpdate(instance: selectedTeam) - } catch { - Logger.error(error) - } - - self.selectedTeam = nil - } - - Button("Annuler", role: .cancel) { - self.selectedTeam = nil - } - } - } .overlay(content: { if calculating { ProgressView() } }) .onAppear { - let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil }) + let rankingPublished = tournament.selectedSortedTeams().anySatisfy({ $0.finalRanking != nil }) if rankingPublished == false { - calculating = true Task { await _calculateRankings() - calculating = false } } } @@ -174,130 +123,178 @@ struct TournamentRankView: View { .toolbarBackground(.visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .topBarTrailing) { - if let url = tournament.shareURL(.rankings) { - _actionForURL(url) - } + EditButton() } } } struct TeamRankCellView: View { + @Environment(\.editMode) private var editMode @Environment(Tournament.self) var tournament: Tournament - let team: TeamRegistration - let key: Int + @EnvironmentObject var dataStore: DataStore + @State private var isEditingTeam: Bool = false + @Bindable var team: TeamRegistration + @State var key: Int var body: some View { - HStack { - VStack(alignment: .trailing) { - VStack(alignment: .trailing, spacing: -8.0) { - ZStack(alignment: .trailing) { - Text(tournament.teamCount.formatted()).hidden() - Text(key.formatted()) - } - .monospacedDigit() - .font(.largeTitle) - .fontWeight(.bold) - Text(key.ordinalFormattedSuffix()).font(.caption) - } - if let index = tournament.indexOf(team: team) { - let rankingDifference = index - (key - 1) - if rankingDifference > 0 { - HStack(spacing: 0.0) { - Text(rankingDifference.formatted(.number.sign(strategy: .always()))) - .monospacedDigit() - Image(systemName: "arrowtriangle.up.fill") - .imageScale(.small) - } - .foregroundColor(.green) - } else if rankingDifference < 0 { - HStack(spacing: 0.0) { - Text(rankingDifference.formatted(.number.sign(strategy: .always()))) - .monospacedDigit() - Image(systemName: "arrowtriangle.down.fill") - .imageScale(.small) + VStack(spacing: 0) { + if editMode?.wrappedValue.isEditing == true { + if key > 1 { + Button { + key -= 1 + team.finalRanking = key + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) } - .foregroundColor(.red) - } else { - Text("--") + } label: { + Label("descendre", systemImage: "chevron.compact.up").labelStyle(.iconOnly) } + .buttonStyle(.bordered) } } - - Divider() - - VStack(alignment: .leading) { - if let name = team.name { - Text(name).foregroundStyle(.secondary) - } - - ForEach(team.players()) { player in - VStack(alignment: .leading, spacing: -4.0) { - Text(player.playerLabel()).bold() - HStack(alignment: .firstTextBaseline, spacing: 0.0) { - Text(player.rankLabel()) - if let rank = player.getRank() { - Text(rank.ordinalFormattedSuffix()) - .font(.caption) + Button { + isEditingTeam = true + } label: { + HStack { + VStack(alignment: .trailing) { + VStack(alignment: .trailing, spacing: -8.0) { + ZStack(alignment: .trailing) { + Text(tournament.teamCount.formatted()).hidden() + Text(key.formatted()) + } + .monospacedDigit() + .font(.largeTitle) + .fontWeight(.bold) + Text(key.ordinalFormattedSuffix()).font(.caption) + } + if let index = tournament.indexOf(team: team) { + let rankingDifference = index - (key - 1) + if rankingDifference > 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.up.fill") + .imageScale(.small) + } + .foregroundColor(.green) + } else if rankingDifference < 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.down.fill") + .imageScale(.small) + } + .foregroundColor(.red) + } else { + Text("--") + } + } + } + + + Divider() + + VStack(alignment: .leading) { + if let name = team.name { + Text(name).foregroundStyle(.secondary) + } + + ForEach(team.players()) { player in + VStack(alignment: .leading, spacing: -4.0) { + Text(player.playerLabel()).bold() + HStack(alignment: .firstTextBaseline, spacing: 0.0) { + Text(player.rankLabel()) + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()) + .font(.caption) + } + } + } + } + } + if tournament.isAnimation() == false && key > 0 { + Spacer() + VStack(alignment: .trailing) { + HStack(alignment: .lastTextBaseline, spacing: 0.0) { + Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always()))) + Text("pts").font(.caption) } } } } + .frame(maxWidth: .infinity) } - - if tournament.isAnimation() == false && key > 0 { - Spacer() - VStack(alignment: .trailing) { - HStack(alignment: .lastTextBaseline, spacing: 0.0) { - Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always()))) - Text("pts").font(.caption) + .contentShape(Rectangle()) + .buttonStyle(.plain) + + if editMode?.wrappedValue.isEditing == true { + Button { + key += 1 + team.finalRanking = key + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) } + } label: { + Label("descendre", systemImage: "chevron.compact.down").labelStyle(.iconOnly) } + .buttonStyle(.bordered) } } - } - } - - private func _publishRankings() { - rankings.keys.sorted().forEach { rank in - if let rankedTeams = rankings[rank] { - rankedTeams.forEach { team in - team.finalRanking = rank - team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount) + .alert("Position", isPresented: $isEditingTeam) { + TextField("Position", value: $team.finalRanking, format: .number) + .keyboardType(.numberPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + + Button("Valider") { + team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount) + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + + isEditingTeam = false + } + + Button("Annuler", role: .cancel) { + isEditingTeam = false } } } - _save() } - + private func _calculateRankings() async { + await MainActor.run { + calculating = true + } + let finalRanks = await tournament.finalRanking() finalRanks.keys.sorted().forEach { rank in if let rankedTeamIds = finalRanks[rank] { rankings[rank] = rankedTeamIds.compactMap { Store.main.findById($0) } } } - } - - @ViewBuilder - private func _actionForURL(_ url: URL, removeSource: Bool = false) -> some View { - Menu { - Button { - UIApplication.shared.open(url) - } label: { - Label("Voir", systemImage: "safari") + + await MainActor.run { + rankings.keys.sorted().forEach { rank in + if let rankedTeams = rankings[rank] { + rankedTeams.forEach { team in + team.finalRanking = rank + team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount) + } + } } + _save() - ShareLink(item: url) { - Label("Partager le lien", systemImage: "link") - } - } label: { - Image(systemName: "square.and.arrow.up") + calculating = false } - .frame(maxWidth: .infinity) - .buttonStyle(.borderless) } - private func _save() { do { diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index b443928..1353b44 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -89,12 +89,17 @@ struct TournamentBuildView: View { Section { + #if DEBUG + NavigationLink(value: Screen.rankings) { + Text("Classement final des équipes") + } + #else if tournament.hasEnded() { NavigationLink(value: Screen.rankings) { Text("Classement final des équipes") } } - + #endif if state == .running || state == .finished { TournamentInscriptionView(tournament: tournament) TournamentBroadcastRowView(tournament: tournament) diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index f042080..27baa28 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -99,7 +99,7 @@ final class ServerDataTests: XCTestCase { return } - let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyBracket: true, shouldVerifyGroupStage: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true) + let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyBracket: true, shouldVerifyGroupStage: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true) let t = try await Store.main.service().post(tournament) assert(t.event == tournament.event) @@ -138,6 +138,7 @@ final class ServerDataTests: XCTestCase { assert(t.hideTeamsWeight == tournament.hideTeamsWeight) assert(t.publishTournament == tournament.publishTournament) assert(t.hidePointsEarned == tournament.hidePointsEarned) + assert(t.publishRankings == tournament.publishRankings) } func testGroupStage() async throws { From 4fad76f79747a449ff38d83738b5a77c855d329a Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 1 Jul 2024 17:30:39 +0200 Subject: [PATCH 7/8] fix crash --- PadelClub/Views/Tournament/TournamentView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 4223a4f..bef203d 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -78,6 +78,7 @@ struct TournamentView: View { } } } + .environment(tournament) .id(tournament.id) .toolbarBackground(.visible, for: .navigationBar) .navigationDestination(for: Screen.self, destination: { screen in From 443d06be74b9e0c4e88feee08c6da4aefe5e5e12 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 1 Jul 2024 17:47:55 +0200 Subject: [PATCH 8/8] fix post merge --- PadelClub/Data/Match.swift | 18 ++---------------- PadelClub/Views/Navigation/MainView.swift | 2 +- PadelClub/Views/Round/LoserRoundView.swift | 3 +-- .../Tournament/Screen/TournamentRankView.swift | 7 +++---- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index bef1ef7..738d2d6 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -223,7 +223,7 @@ defer { let existingTeamScore = teamScore(ofTeam: team) ?? TeamScore(match: id, team: team) existingTeamScore.walkOut = 1 do { - try DataStore.shared.teamScores.addOrUpdate(instance: existingTeamScore) + try self.tournamentStore.teamScores.addOrUpdate(instance: existingTeamScore) } catch { Logger.error(error) } @@ -239,27 +239,13 @@ defer { func setLuckyLoser(team: TeamRegistration, teamPosition: TeamPosition) { resetMatch() - let previousScores = teamScores.filter({ $0.luckyLoser != nil }) - do { - try self.tournamentStore.teamScores.delete(contentOfs: previousScores) - } catch { - Logger.error(error) - } - - if let existingTeamScore = teamScore(ofTeam: team) { - do { - try self.tournamentStore.teamScores.delete(instance: existingTeamScore) - } catch { - Logger.error(error) - } - } let matchIndex = index let position = matchIndex * 2 + teamPosition.rawValue let previousScores = teamScores.filter({ $0.luckyLoser == position }) do { - try DataStore.shared.teamScores.delete(contentOfs: previousScores) + try self.tournamentStore.teamScores.delete(contentOfs: previousScores) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index eeaf717..c7f7001 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -87,7 +87,7 @@ struct MainView: View { } .environmentObject(dataStore) .task { - await self._checkSourceFileAvailability() + //await self._checkSourceFileAvailability() if StoreCenter.main.hasToken() { do { try await dataStore.clubs.loadDataFromServerIfAllowed() diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index 5b50bd9..19fc8bc 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -9,7 +9,6 @@ import SwiftUI import LeStorage struct LoserRoundView: View { - @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed @@ -93,7 +92,7 @@ struct LoserRoundView: View { let allRoundMatches = loserBracket.allMatches allRoundMatches.forEach({ $0.name = $0.roundTitle() }) do { - try DataStore.shared.matches.addOrUpdate(contentOfs: allRoundMatches) + try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) } catch { Logger.error(error) } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index f9fd065..b9ed6b2 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -160,7 +160,6 @@ struct TournamentRankView: View { struct TeamRankCellView: View { @Environment(\.editMode) private var editMode @Environment(Tournament.self) var tournament: Tournament - @EnvironmentObject var dataStore: DataStore @State private var isEditingTeam: Bool = false @Bindable var team: TeamRegistration @State var key: Int @@ -173,7 +172,7 @@ struct TournamentRankView: View { key -= 1 team.finalRanking = key do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -264,7 +263,7 @@ struct TournamentRankView: View { key += 1 team.finalRanking = key do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) } @@ -283,7 +282,7 @@ struct TournamentRankView: View { Button("Valider") { team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount) do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) } catch { Logger.error(error) }