diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index cbe10a0..5b67a94 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -226,6 +226,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 */; }; @@ -239,8 +240,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 */; }; @@ -557,6 +556,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 = ""; }; @@ -570,7 +570,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 = ""; }; @@ -597,7 +596,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */, FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */, FF92660D2C241CE0002361A4 /* Zip in Frameworks */, C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */, @@ -646,7 +644,6 @@ isa = PBXGroup; children = ( FF92660F2C255E4A002361A4 /* PadelClub.entitlements */, - FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */, FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */, FFA6D78A2BB0BEB3003A31F3 /* Info.plist */, C4EC6F562BE92CAC000CEAB4 /* local.plist */, @@ -1259,6 +1256,7 @@ isa = PBXGroup; children = ( FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */, + FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */, ); path = Components; sourceTree = ""; @@ -1343,7 +1341,6 @@ C425D3FA2B6D249D002A7B48 /* Frameworks */, C425D3FB2B6D249D002A7B48 /* Resources */, FF2BE4892B85E27400592328 /* Embed Frameworks */, - FFE2D2D62C216C1700D0C7BE /* ShellScript */, ); buildRules = ( ); @@ -1352,7 +1349,6 @@ name = PadelClub; packageProductDependencies = ( FFCFBFFD2BBBE86600B82851 /* Algorithms */, - FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */, FF92660C2C241CE0002361A4 /* Zip */, ); productName = PadelClub; @@ -1430,7 +1426,6 @@ mainGroup = C425D3F42B6D249D002A7B48; packageReferences = ( FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */, - FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */, ); productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */; @@ -1460,7 +1455,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 */, @@ -1486,31 +1480,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; @@ -1549,6 +1518,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 */, @@ -1916,7 +1886,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 = 76; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1940,7 +1910,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)"; @@ -1957,7 +1926,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 = 76; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1980,7 +1949,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)"; @@ -2124,14 +2092,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 */ @@ -2145,11 +2105,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/Data/Club.swift b/PadelClub/Data/Club.swift index 8374a40..44b2eaf 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -76,7 +76,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 { @@ -231,10 +231,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: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 14333da..7178937 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -38,7 +38,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 && $0.isDeleted == false } } func clubObject() -> Club? { @@ -47,7 +47,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 3c515e3..10d2ec5 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -200,7 +200,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) @@ -211,7 +211,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) @@ -222,7 +222,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) @@ -234,7 +234,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) @@ -245,7 +245,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) @@ -332,7 +332,6 @@ class GroupStage: ModelObject, Storable { func unsortedTeams() -> [TeamRegistration] { return self.tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } -// Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } } func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { @@ -433,7 +432,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 d5822eb..e463920 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -69,7 +69,6 @@ class Match: ModelObject, Storable { var teamScores: [TeamScore] { return self.tournamentStore.teamScores.filter { $0.match == self.id } -// return Store.main.filter { $0.match == self.id } } // MARK: - @@ -82,6 +81,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 }) { @@ -99,6 +105,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) } @@ -300,15 +313,12 @@ class Match: ModelObject, Storable { return self.tournamentStore.matches.first(where: { $0.round == round && $0.index == lookingForIndex }) -// return Store.main.filter(isIncluded: { $0.round == round && $0.index == lookingForIndex }).first } private func _forwardMatch(inRound round: Round) -> Match? { guard let roundObjectNextRound = round.nextRound() else { return nil } let nextIndex = (index - 1) / 2 - return self.tournamentStore.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }) -// return Store.main.filter(isIncluded: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }).first } func _toggleForwardMatchDisableState(_ state: Bool) { @@ -378,7 +388,6 @@ class Match: ModelObject, Storable { func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? { return self.tournamentStore.matches.first(where: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }) -// return Store.main.filter(isIncluded: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }).first } func getDuration() -> Int { @@ -405,26 +414,20 @@ class Match: ModelObject, Storable { func topPreviousRoundMatch() -> Match? { guard let roundObject else { return nil } - let matches: [Match] = self.tournamentStore.matches.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 self.tournamentStore.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] = self.tournamentStore.matches.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 self.tournamentStore.matches.first(where: { match in + match.round != nil && match.round == roundObjectPreviousRoundId && match.index == bottomPreviousRoundMatchIndex + }) } func previousMatch(_ teamPosition: TeamPosition) -> Match? { @@ -435,11 +438,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() @@ -447,9 +445,9 @@ class Match: ModelObject, Storable { func previousMatches() -> [Match] { guard let roundObject else { return [] } + let roundObjectPreviousRoundId = roundObject.previousRound()?.id return self.tournamentStore.matches.filter { match in - (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) - && match.round == roundObject.previousRound()?.id + match.round == roundObjectPreviousRoundId && (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) }.sorted(by: \.index) } @@ -683,10 +681,22 @@ class Match: ModelObject, Storable { } 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)? { @@ -715,7 +725,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 { @@ -729,7 +740,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) @@ -737,19 +748,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 1f7b919..94529a7 100644 --- a/PadelClub/Data/MonthData.swift +++ b/PadelClub/Data/MonthData.swift @@ -42,7 +42,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/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index b1a2e36..09152db 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -81,8 +81,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/Data/Round.swift b/PadelClub/Data/Round.swift index 28ae44e..0ece396 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -41,7 +41,7 @@ class Round: ModelObject, Storable { } func _matches() -> [Match] { - return self.tournamentStore.matches.filter { $0.round == self.id } + return self.tournamentStore.matches.filter { $0.round == self.id }.sorted(by: \.index) // return Store.main.filter { $0.round == self.id } } @@ -66,7 +66,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] { @@ -81,8 +85,8 @@ class Round: ModelObject, Storable { func previousMatches(ofMatch match: Match) -> [Match] { guard let previousRound = previousRound() else { return [] } - return self.tournamentStore.filter { - ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) && $0.round == previousRound.id + return self.tournamentStore.matches.filter { + $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) } // return Store.main.filter { @@ -103,13 +107,8 @@ 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? { @@ -119,17 +118,11 @@ class Round: ModelObject, Storable { && ($0.bracketPosition! / 2) == matchIndex && ($0.bracketPosition! % 2) == team.rawValue }) - -// return Store.main.filter(isIncluded: { -// $0.tournament == tournament -// && $0.bracketPosition != nil -// && ($0.bracketPosition! / 2) == matchIndex -// && ($0.bracketPosition! % 2) == team.rawValue -// }).first } func seeds(inMatchIndex matchIndex: Int) -> [TeamRegistration] { return self.tournamentStore.teamRegistrations.filter { + $0.tournament == tournament && $0.bracketPosition != nil && ($0.bracketPosition! / 2) == matchIndex @@ -162,7 +155,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 } @@ -171,86 +171,116 @@ 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 self.tournamentStore.teamRegistrations.findById(parent) - } else if let previousMatch = topPreviousRoundMatch(ofMatch: match) { + } else if let previousMatch = topPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return self.tournamentStore.teamRegistrations.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 self.tournamentStore.teamRegistrations.findById(parent) - } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match) { + } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return self.tournamentStore.teamRegistrations.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] = self.tournamentStore.matches.filter { - $0.index == match.topPreviousRoundMatchIndex() && $0.round == previousRound.id - } -// 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 self.tournamentStore.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] = self.tournamentStore.matches.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 self.tournamentStore.matches.first(where: { + $0.round == previousRound.id && $0.index == bottomPreviousRoundMatchIndex + }) } func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? { - return self.tournamentStore.matches.first(where: { + self.tournamentStore.matches.first(where: { let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) return $0.round == id && index == matchIndexInRound }) - -// Store.main.filter(isIncluded: { -// let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) -// return $0.round == id && index == matchIndexInRound -// }).first } func enabledMatches() -> [Match] { - return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false } -// return Store.main.filter { $0.round == self.id && $0.disabled == false } + return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index) } 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) @@ -269,18 +299,28 @@ class Round: ModelObject, Storable { } func previousRound() -> Round? { - return self.tournamentStore.rounds.first(where: { $0.tournament == tournament && $0.parent == parent && $0.index == index + 1 }) -// 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 self.tournamentStore.rounds.first(where: { $0.parent == parent && $0.index == index + 1 }) } func nextRound() -> Round? { - return self.tournamentStore.rounds.first(where: { $0.tournament == tournament && $0.parent == parent && $0.index == index - 1 }) + return self.tournamentStore.rounds.first(where: { $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 }) } @@ -387,11 +427,16 @@ 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] = self.tournamentStore.teamRegistrations.filter { - $0.tournament == tournament - && $0.bracketPosition != nil + $0.bracketPosition != nil && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex } @@ -406,26 +451,40 @@ class Round: ModelObject, Storable { } func hasNextRound() -> Bool { - return nextRound()?.isDisabled() == false + 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] = self.tournamentStore.teamRegistrations.filter { - $0.tournament == tournament - && $0.bracketPosition != nil + $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 } @@ -462,8 +521,16 @@ class Round: ModelObject, Storable { } func loserRounds() -> [Round] { - let rounds: [Round] = self.tournamentStore.rounds.filter { $0.parent == id } - return rounds.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 self.tournamentStore.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed() } func loserRoundsAndChildren() -> [Round] { @@ -604,7 +671,7 @@ extension Round: Selectable, Equatable { } - func selectionLabel() -> String { + func selectionLabel(index: Int) -> String { if let parentRound { return "Tour #\(parentRound.loserRounds().count - index)" } else { @@ -613,6 +680,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 { @@ -625,6 +701,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 d4f14f3..e20446e 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -66,7 +66,6 @@ class TeamRegistration: ModelObject, Storable { func unsortedPlayers() -> [PlayerRegistration] { return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id } -// Store.main.filter { $0.teamRegistration == self.id } } // MARK: - @@ -144,23 +143,19 @@ class TeamRegistration: ModelObject, Storable { } func teamScores() -> [TeamScore] { - return self.tournamentStore.teamScores.filter { $0.teamRegistration == id } -// return Store.main.filter(isIncluded: { $0.teamRegistration == id }) + return self.tournamentStore.teamScores.filter({ $0.teamRegistration == id }) } func wins() -> [Match] { - return self.tournamentStore.matches.filter { $0.winningTeamId == id } -// return Store.main.filter(isIncluded: { $0.winningTeamId == id }) + return self.tournamentStore.matches.filter({ $0.winningTeamId == id }) } func loses() -> [Match] { - return self.tournamentStore.matches.filter { $0.losingTeamId == id } -// return Store.main.filter(isIncluded: { $0.losingTeamId == id }) + return self.tournamentStore.matches.filter({ $0.losingTeamId == id }) } func matches() -> [Match] { - return self.tournamentStore.matches.filter { $0.losingTeamId == id || $0.winningTeamId == id } -// return Store.main.filter(isIncluded: { $0.losingTeamId == id || $0.winningTeamId == id }) + return self.tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id }) } var tournamentCategory: TournamentCategory { @@ -333,10 +328,8 @@ class TeamRegistration: ModelObject, Storable { typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool func players() -> [PlayerRegistration] { - - let playerRegistration = self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id } - - return playerRegistration.sorted { (lhs, rhs) in + + self.tournamentStore.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 }, @@ -391,14 +384,12 @@ class TeamRegistration: ModelObject, Storable { guard let bracketPosition else { return nil } let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) return self.tournamentStore.rounds.first(where: { $0.index == roundIndex }) -// return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == roundIndex }).first } func initialMatch() -> Match? { guard let bracketPosition else { return nil } guard let initialRoundObject = initialRound() else { return nil } return self.tournamentStore.matches.first(where: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }) -// return Store.main.filter(isIncluded: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }).first } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 2903b6c..40ddee2 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -486,8 +486,16 @@ class Tournament : ModelObject, Storable { } func courtUsed() -> [Int] { + +#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] = self.tournamentStore.matches.filter { $0.isRunning() } -// let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id }) return Set(runningMatches.compactMap { $0.courtIndex }).sorted() } @@ -553,12 +561,7 @@ class Tournament : ModelObject, Storable { } 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] { @@ -568,6 +571,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() } } @@ -758,19 +770,16 @@ class Tournament : ModelObject, Storable { } func allMatches() -> [Match] { - let unsortedGroupStages: [GroupStage] = Store.main.filter { $0.tournament == self.id } - let matches: [Match] = self.allGroupStages().flatMap { $0._matches() } + allRoundMatches() - return matches.filter({ $0.disabled == false }) + return self.tournamentStore.matches.filter { $0.disabled == false } } func _allMatchesIncludingDisabled() -> [Match] { - return self.allGroupStages().flatMap { $0._matches() } + allRounds().flatMap { $0._matches() } + return Array(self.tournamentStore.matches) } func rounds() -> [Round] { let rounds: [Round] = self.tournamentStore.rounds.filter { $0.parent == nil } return rounds.sorted(by: \.index).reversed() -// Store.main.filter { $0.tournament == self.id && $0.parent == nil }.sorted(by: \.index).reversed() } func sortedTeams() -> [TeamRegistration] { @@ -779,7 +788,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) @@ -876,7 +885,7 @@ class Tournament : ModelObject, Storable { } func unsortedPlayers() -> [PlayerRegistration] { - return self.unsortedTeams().flatMap { $0.unsortedPlayers() } + return Array(self.tournamentStore.playerRegistrations) } func selectedPlayers() -> [PlayerRegistration] { @@ -892,7 +901,7 @@ class Tournament : ModelObject, Storable { } func players() -> [PlayerRegistration] { - return self.unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) + return self.tournamentStore.playerRegistrations.sorted(by: \.computedRank) } func unrankValue(for malePlayer: Bool) -> Int? { @@ -1029,13 +1038,12 @@ class Tournament : ModelObject, Storable { } func groupStagesMatches() -> [Match] { - let groupStageIds = groupStages().map { $0.id } - return self.tournamentStore.matches.filter { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) } + return self.tournamentStore.matches.filter { $0.groupStage != nil } // return Store.main.filter(isIncluded: { $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) @@ -1046,7 +1054,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) @@ -1057,7 +1065,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) @@ -1068,7 +1076,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) @@ -1079,7 +1087,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) @@ -1110,22 +1118,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) } } } @@ -1234,6 +1265,15 @@ class Tournament : ModelObject, Storable { 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() } @@ -1261,6 +1301,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 }) } @@ -1362,20 +1410,28 @@ class Tournament : ModelObject, Storable { 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) } } @@ -1383,10 +1439,10 @@ class Tournament : ModelObject, Storable { 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) } @@ -1403,16 +1459,12 @@ class Tournament : ModelObject, Storable { } 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() { @@ -1490,6 +1542,7 @@ class Tournament : ModelObject, Storable { if let round: Round = self.getRound(atRoundIndex: roundIndex) { return self.tournamentStore.matches.first(where: { $0.round == round.id && $0.index == matchIndex }) // return Store.main.filter(isIncluded: { $0.round == round.id && $0.index == matchIndex }).first + } return nil } @@ -1754,7 +1807,6 @@ class Tournament : ModelObject, Storable { guard let rankSourceDate else { return nil } let dateString = URL.importDateFormatter.string(from: rankSourceDate) return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) -// return Store.main.filter(isIncluded: { $0.monthKey == dateString }).first } var maleUnrankedValue: Int? { @@ -1812,6 +1864,32 @@ class Tournament : ModelObject, Storable { } + 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/Data/User.swift b/PadelClub/Data/User.swift index e04a306..429e1c0 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -87,11 +87,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/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/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 - } -} 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/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/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/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 93e778e..91adb31 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -25,7 +25,7 @@ struct SeedInterval: Hashable, Comparable { func isFixed() -> Bool { first == 1 && last == 2 } - + var count: Int { dimension } @@ -44,7 +44,15 @@ struct SeedInterval: Hashable, Comparable { return nil } } - + + 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/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/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index 23721d9..038d34d 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -51,7 +51,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/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..d4716dd 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -9,11 +9,14 @@ 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 { - 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 { @@ -45,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] { @@ -106,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 5fb4cb2..d45df30 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -22,7 +22,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 @@ -81,6 +81,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 { @@ -88,10 +90,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/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index 493e5e1..913862f 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: StoreCenter.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/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index 801432c..b5ea93e 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/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/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 215c7ba..e175129 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -23,6 +23,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 = self.tournamentStore.teamRegistrations.findById(id) { + Menu { + Button("Retirer de sa poule") { + team.resetGroupeStagePosition() + do { + try self.tournamentStore.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/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/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/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 2fd74fc..6a17ec1 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -100,11 +100,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() } } @@ -240,6 +238,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() @@ -440,7 +447,15 @@ struct MatchDetailView: View { } fileprivate func _editScores() { - + if match.isReady() == false && match.teams().count == 2 { + let teamsScores = match.getOrCreateTeamScores() + do { + try self.tournamentStore.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 ebe8b61..7bb8010 100644 --- a/PadelClub/Views/Match/MatchRowView.swift +++ b/PadelClub/Views/Match/MatchRowView.swift @@ -12,12 +12,13 @@ struct MatchRowView: View { var match: Match var tournament: Tournament 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) @@ -60,7 +61,7 @@ struct MatchRowView: View { MatchDetailView(match: match, matchViewStyle: matchViewStyle) .environment(self.tournament) } 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/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 3f7844f..8a6b377 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -12,7 +12,8 @@ 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) { @@ -20,6 +21,11 @@ struct ToolboxView: View { Section { Text("Version de l'application").badge(PadelClubApp.appVersion) + .onTapGesture(count: 5) { + StoreCenter.main.resetApiCalls() + didResetApiCalls = true + } + SupportButtonView(contentIsUnavailable: false) if StoreCenter.main.userId == "94f45ed2-8938-4c32-a4b6-e4525073dd33" { @@ -157,6 +163,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/Planning/SchedulerView.swift b/PadelClub/Views/Planning/SchedulerView.swift index efbaed4..19d593d 100644 --- a/PadelClub/Views/Planning/SchedulerView.swift +++ b/PadelClub/Views/Planning/SchedulerView.swift @@ -56,13 +56,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) } } @@ -123,19 +125,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 self.tournamentStore.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 self.tournamentStore.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) } } @@ -166,7 +170,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/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index b758bd2..4f926ae 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -102,8 +102,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/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index cbfe522..ac0ae9d 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -10,41 +10,58 @@ 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() } - ForEach(loserRounds) { loserRound in - if true { + ForEach(loserBracket.rounds) { loserRound in + let matches = _matches(loserRoundId: loserRound.id) + if matches.isEmpty == false { Section { - let matches = loserRound.playedMatches().sorted(by: \.index) ForEach(matches) { match in MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle) .overlay { - if match.disabled /*&& isEditingTournamentSeed*/ { + if match.disabled && isEditingTournamentSeed.wrappedValue == true { Image(systemName: "xmark") .resizable() .scaledToFit() - .opacity(0.8) + .opacity(0.6) } } .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 { - Text(loserRound.roundTitle(.wide)) - let tournamentTeamCount = tournament.teamCount - if let seedIntervalPointRange = loserRound.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + if let seedInterval = loserRound.seedInterval() { + Text(seedInterval.localizedLabel(.wide)) + let seedIntervalPointRange = seedInterval.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournament.teamCount) Spacer() Text(seedIntervalPointRange) .font(.caption) @@ -53,12 +70,23 @@ struct LoserRoundView: View { } } } + + /* + 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() } } } @@ -67,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/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index f23d006..ed6246f 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -22,6 +22,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 = self.tournamentStore.teamRegistrations.findById(id) { + Menu { + Button("Retirer du tableau") { + team.resetBracketPosition() + do { + try self.tournamentStore.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/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index aad0bd7..c4c43ce 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -22,20 +22,36 @@ struct RoundView: View { @State private var availableSeedGroup: SeedInterval? @State private var showPrintScreen: Bool = false - var round: Round + var upperRound: UpperRound var tournamentStore: TournamentStore { return self.tournament.tournamentStore } 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 { @@ -57,8 +73,7 @@ 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) if displayableMatches.isEmpty { Section { ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball") @@ -71,30 +86,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") } } @@ -106,7 +129,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 @@ -230,16 +253,17 @@ struct RoundView: View { } ForEach(displayableMatches) { match in + let matchTitle = match.matchTitle(.short, inMatches: displayableMatches) Section { - MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle) + MatchRowView(match: match, tournament: self.tournament, 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) @@ -262,15 +286,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..db02f53 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 { + if tournament.shouldVerifyBracket { _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/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index e8749da..4db4bef 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: @@ -142,7 +148,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 +449,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 eabc8de..09fda0d 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() @@ -34,264 +34,245 @@ struct BroadcastView: View { List { if StoreCenter.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) } 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 { 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/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 8fbe337..8fe1a3c 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -58,7 +58,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] = [] @@ -124,7 +123,6 @@ struct InscriptionManagerView: View { private func _clearScreen() { teamPaste = nil unsortedPlayers.removeAll() - unfilteredTeams.removeAll() walkoutTeams.removeAll() unsortedTeamsWithoutWO.removeAll() sortedTeams.removeAll() @@ -143,7 +141,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) @@ -187,7 +185,7 @@ struct InscriptionManagerView: View { _managementView() if _isEditingTeam() { _buildingTeamView() - } else if unfilteredTeams.isEmpty { + } else if sortedTeams.isEmpty { _inscriptionTipsView() } else { _teamRegisteredView() @@ -332,6 +330,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) { @@ -437,7 +444,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) @@ -452,7 +459,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) @@ -460,6 +467,9 @@ struct InscriptionManagerView: View { } #endif sortedTeams = tournament.sortedTeams() + } + + var filteredTeams: [TeamRegistration] { var teams = sortedTeams if filterMode == .walkOut { @@ -471,14 +481,14 @@ struct InscriptionManagerView: View { } if byDecreasingOrdering { - self.unfilteredTeams = teams.reversed() + return teams.reversed() } else { - self.unfilteredTeams = teams + return teams } } 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) @@ -502,10 +512,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 { @@ -716,7 +726,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) @@ -878,7 +888,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 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 49481e9..5231dfc 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -19,6 +19,9 @@ struct TournamentRankView: View { var tournamentStore: TournamentStore { return self.tournament.tournamentStore } + + @State private var runningMatches: [Match]? + @State private var matchesLeft: [Match]? var isEditingTeam: Binding { Binding { @@ -30,11 +33,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") } @@ -114,6 +130,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..bb7bb55 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" @@ -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/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/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 7f4954d..b443928 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? @@ -64,8 +64,10 @@ struct TournamentBuildView: View { } label: { Text("Tableau") if tournament.shouldVerifyBracket { - Text("Vérifier la tableau").foregroundStyle(.logoRed) - } else if let range = bracketStatus?.1 { + Text("Vérifier le tableau").foregroundStyle(.logoRed) + } 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..7c0d6fd 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.localizedTournamentType()) } label: { LabelSettings() + Text("Formats, club, prix et plus") } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 71eb57e..4223a4f 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -45,9 +45,6 @@ struct TournamentView: View { var body: some View { VStack(spacing: 0.0) { List { - TipView(tournamentRunningTip) - .tipStyle(tint: nil) - if tournament.state() != .finished { SubscriptionInfoView() } @@ -194,6 +191,7 @@ struct TournamentView: View { } } label: { LabelOptions() + .popoverTip(tournamentRunningTip) } } } diff --git a/PadelClub/Views/User/LoginView.swift b/PadelClub/Views/User/LoginView.swift index 0b50702..cb36838 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) }