multistore
Laurent 1 year ago
commit b99883732b
  1. 57
      PadelClub.xcodeproj/project.pbxproj
  2. 8
      PadelClub/Data/Club.swift
  3. 4
      PadelClub/Data/Event.swift
  4. 13
      PadelClub/Data/GroupStage.swift
  5. 87
      PadelClub/Data/Match.swift
  6. 2
      PadelClub/Data/MonthData.swift
  7. 11
      PadelClub/Data/PlayerRegistration.swift
  8. 219
      PadelClub/Data/Round.swift
  9. 19
      PadelClub/Data/TeamRegistration.swift
  10. 156
      PadelClub/Data/Tournament.swift
  11. 4
      PadelClub/Data/User.swift
  12. 6
      PadelClub/Extensions/String+Extensions.swift
  13. 30
      PadelClub/GoogleService-Info.plist
  14. 9
      PadelClub/PadelClubApp.swift
  15. 10
      PadelClub/Utils/FileImportManager.swift
  16. 2
      PadelClub/Utils/PadelRule.swift
  17. 2
      PadelClub/Utils/URLs.swift
  18. 2
      PadelClub/ViewModel/AgendaDestination.swift
  19. 4
      PadelClub/ViewModel/FederalDataViewModel.swift
  20. 8
      PadelClub/ViewModel/SeedInterval.swift
  21. 2
      PadelClub/ViewModel/Selectable.swift
  22. 2
      PadelClub/Views/Calling/CallSettingsView.swift
  23. 55
      PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift
  24. 2
      PadelClub/Views/Calling/GroupStageCallingView.swift
  25. 23
      PadelClub/Views/Calling/SeedsCallingView.swift
  26. 8
      PadelClub/Views/Calling/SendToAllView.swift
  27. 19
      PadelClub/Views/Cashier/Event/EventCreationView.swift
  28. 2
      PadelClub/Views/Cashier/Event/EventView.swift
  29. 2
      PadelClub/Views/Club/ClubsView.swift
  30. 5
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  31. 38
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  32. 2
      PadelClub/Views/GroupStage/GroupStagesView.swift
  33. 12
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  34. 9
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  35. 19
      PadelClub/Views/Match/MatchDetailView.swift
  36. 5
      PadelClub/Views/Match/MatchRowView.swift
  37. 8
      PadelClub/Views/Match/MatchSummaryView.swift
  38. 18
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  39. 19
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  40. 6
      PadelClub/Views/Planning/SchedulerView.swift
  41. 4
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  42. 60
      PadelClub/Views/Round/LoserRoundView.swift
  43. 140
      PadelClub/Views/Round/LoserRoundsView.swift
  44. 39
      PadelClub/Views/Round/RoundSettingsView.swift
  45. 68
      PadelClub/Views/Round/RoundView.swift
  46. 16
      PadelClub/Views/Round/RoundsView.swift
  47. 24
      PadelClub/Views/Tournament/FileImportView.swift
  48. 124
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  49. 14
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  50. 11
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  51. 36
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  52. 2
      PadelClub/Views/Tournament/Screen/TournamentCallView.swift
  53. 6
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  54. 25
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  55. 4
      PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift
  56. 2
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift
  57. 12
      PadelClub/Views/Tournament/TournamentBuildView.swift
  58. 10
      PadelClub/Views/Tournament/TournamentInitView.swift
  59. 4
      PadelClub/Views/Tournament/TournamentView.swift
  60. 2
      PadelClub/Views/User/LoginView.swift

@ -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 = "<group>"; };
FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; };
FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = "<group>"; };
FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = "<group>"; };
FFCFC0012BBC39A600B82851 /* EditScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScoreView.swift; sourceTree = "<group>"; };
FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointSelectionView.swift; sourceTree = "<group>"; };
FFCFC0112BBC3E1A00B82851 /* PointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointView.swift; sourceTree = "<group>"; };
@ -570,7 +570,6 @@
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = "<group>"; };
FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = "<group>"; };
FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */

@ -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)
}

@ -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: -

@ -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()
}

@ -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)
}
}

@ -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

@ -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]) {

@ -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 {
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
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
guard let previousRound else { return nil }
let topPreviousRoundMatchIndex = match.topPreviousRoundMatchIndex()
return self.tournamentStore.matches.first(where: {
$0.round == previousRound.id && $0.index == topPreviousRoundMatchIndex
})
}
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(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
func seedInterval() -> SeedInterval? {
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() {
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
}
}

@ -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 {
@ -334,9 +329,7 @@ class TeamRegistration: ModelObject, Storable {
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
}

@ -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,13 +561,8 @@ 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,+)
}
}
func seeds() -> [TeamRegistration] {
let selectedSortedTeams = selectedSortedTeams()
@ -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)"
}
if description == nil {
let availableQualifiedTeams = availableQualifiedTeams()
if availableQualifiedTeams.isEmpty == false {
return ("placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix, cut)
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,17 +1459,13 @@ 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: ", ")
}
}
func deleteAndBuildEverything() {
deleteStructure()
@ -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 {

@ -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) {

@ -195,3 +195,9 @@ extension String {
return url
}
}
extension String {
func toInt() -> Int? {
Int(self)
}
}

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyAjZC_NmXtQzK5dotExlQjU66fTNylNMII</string>
<key>GCM_SENDER_ID</key>
<string>404879692726</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>app.padelclub</string>
<key>PROJECT_ID</key>
<string>padel-club-8e872</string>
<key>STORAGE_BUCKET</key>
<string>padel-club-8e872.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:404879692726:ios:90e65a062285ac3dd46717</string>
</dict>
</plist>

@ -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<Bool> {
@ -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
}
}

@ -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
}

@ -342,7 +342,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
return .twoSetsDecisivePointSuperTie
}
default:
return .twoSetsDecisivePoint
return .superTie
}
}

@ -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)"

@ -35,7 +35,7 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable {
}
}
func selectionLabel() -> String {
func selectionLabel(index: Int) -> String {
localizedTitleKey
}

@ -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
}

@ -45,6 +45,14 @@ struct SeedInterval: Hashable, Comparable {
}
}
var computedLast: Int {
last - reduce
}
var computedFirst: Int {
first - reduce
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if dimension < 2 {
return "#\(first - reduce) / #\(last - reduce)"

@ -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?

@ -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)
}
}
}

@ -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")
}
}
}

@ -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

@ -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)
}
}
}
}

@ -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)
}

@ -130,6 +130,7 @@ 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)
@ -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!)

@ -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"

@ -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 })

@ -33,11 +33,12 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
.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()

@ -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 {

@ -28,7 +28,7 @@ struct GroupStagesView: View {
}
}
func selectionLabel() -> String {
func selectionLabel(index: Int) -> String {
switch self {
case .all:
return "Tout"

@ -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")

@ -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]? {
@ -37,10 +40,6 @@ struct PlayerBlockView: View {
match.hasWalkoutTeam()
}
var isWalkOut: Bool {
match.teamWalkOut(team)
}
var scores: [String] {
teamScore?.score?.components(separatedBy: ",") ?? []
}

@ -100,13 +100,11 @@ struct MatchDetailView: View {
}
}
if match.isReady() {
Section {
RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
self._editScores()
}
}
}
let players = self.match.teams().flatMap { $0.players() }
let unpaid = players.filter({ $0.hasPaid() == false })
@ -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,6 +447,14 @@ 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 {

@ -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()))
}

@ -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,8 +54,10 @@ struct MatchSummaryView: View {
if let roundTitle {
Text(roundTitle).fontWeight(.semibold)
}
if match.index > 0 {
Text(matchTitle)
}
}
Spacer()
if let courtName {
Spacer()

@ -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 {

@ -12,6 +12,7 @@ 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
@ -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)
}
}

@ -56,6 +56,7 @@ struct SchedulerView: View {
}
} footer: {
if tournament.isAnimation() == false {
if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight {
Button {
tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat()
@ -65,6 +66,7 @@ struct SchedulerView: View {
.buttonStyle(.plain)
}
}
}
ForEach(tournament.groupStages()) {
GroupStageScheduleEditorView(groupStage: $0, tournament: tournament)
@ -123,6 +125,7 @@ struct SchedulerView: View {
} header: {
Text(round.titleLabel())
} footer: {
if tournament.isAnimation() == false {
let federalFormat = tournament.roundSmartMatchFormat(round.index)
if round.matchFormat.weight > federalFormat.weight {
Button {
@ -138,6 +141,7 @@ struct SchedulerView: View {
.buttonStyle(.plain)
}
}
}
if round.index != 0 {
Section {
@ -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 {

@ -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)
}
}

@ -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 loserRounds: [Round]
@State private var isEditingTournamentSeed: Bool = false
let loserBracket: LoserRound
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()
}
}

@ -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]
@State private var isEditingTournamentSeed = false
init(upperBracketRound: Round) {
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: upperBracketRound.loserRounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? upperBracketRound.loserRounds.first(where: { $0.shouldBeDisplayed }))
}
_selectedRound = State(wrappedValue: rounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? rounds.first)
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)
}

@ -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 {

@ -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 {

@ -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)

@ -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)
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 {

@ -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,35 +34,60 @@ 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)
}
}
} else {
Section {
TipView(tournamentPublishingTip) { action in
UIApplication.shared.open(URLs.main.url)
}
.tipStyle(tint: nil)
}
Section {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel()).tag(pageLink)
}
} label: {
Text("Choisir la page à partager")
}
.pickerStyle(.menu)
actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink))
} header: {
Text("Lien du tournoi à partager")
}
Section {
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)
}
if tournament.isPrivate == false {
if let url = tournament.shareURL(.clubBroadcast) {
Section {
Link(destination: url) {
@ -247,51 +272,7 @@ struct BroadcastView: View {
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 url = tournament.shareURL(pageLink) {
LabeledContent {
actionForURL(url)
} label: {
Text("Tournoi")
Text(pageLink.localizedLabel())
}
}
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel()).tag(pageLink)
}
} label: {
Text("Modifier la page du tournoi à partager")
}
.pickerStyle(.menu)
} header: {
Text("Liens à partager")
.textCase(nil)
}
}
.headerProminence(.increased)
.navigationTitle("Publication")
@ -343,7 +324,8 @@ struct BroadcastView: View {
}
@ViewBuilder
func actionForURL(_ url: URL, removeSource: Bool = false) -> some View {
func actionForURL(title: String, description: String? = nil, url: URL?, removeSource: Bool = false) -> some View {
if let url {
Menu {
Button {
UIApplication.shared.open(url)
@ -361,13 +343,29 @@ struct BroadcastView: View {
Label("Partager le lien", systemImage: "link")
}
} label: {
HStack {
Spacer()
LabeledContent {
Image(systemName: "square.and.arrow.up")
.foregroundColor(.master)
} label: {
Text(title)
.foregroundColor(.primary)
if let description {
Text(description)
.foregroundColor(.secondary)
}
}
}
.buttonStyle(.plain)
} else {
LabeledContent {
Image(systemName: "xmark").foregroundColor(.logoYellow)
} label: {
Text(title)
if let description {
Text(description)
}
}
}
.frame(maxWidth: .infinity)
.buttonStyle(.borderless)
}

@ -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 {

@ -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))"))
}
}

@ -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

@ -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"

@ -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"
}

@ -20,6 +20,9 @@ struct TournamentRankView: View {
return self.tournament.tournamentStore
}
@State private var runningMatches: [Match]?
@State private var matchesLeft: [Match]?
var isEditingTeam: Binding<Bool> {
Binding {
selectedTeam != nil
@ -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

@ -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")
}
}

@ -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"

@ -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,7 +100,6 @@ struct TournamentBuildView: View {
TournamentBroadcastRowView(tournament: tournament)
}
if state != .finished {
NavigationLink(value: Screen.schedule) {
let tournamentStatus = scheduleStatus
LabeledContent {
@ -108,7 +109,7 @@ struct TournamentBuildView: View {
ProgressView()
}
} label: {
Text("Horaires")
Text("Horaires et formats")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
@ -140,7 +141,6 @@ struct TournamentBuildView: View {
.task {
callStatus = await tournament.callStatus()
}
}
NavigationLink(value: Screen.cashier) {
let tournamentStatus = cashierStatus

@ -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")
}
}

@ -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)
}
}
}

@ -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)
}

Loading…
Cancel
Save