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. 227
      PadelClub/Data/Round.swift
  9. 21
      PadelClub/Data/TeamRegistration.swift
  10. 166
      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. 12
      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. 21
      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. 11
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  35. 27
      PadelClub/Views/Match/MatchDetailView.swift
  36. 7
      PadelClub/Views/Match/MatchRowView.swift
  37. 10
      PadelClub/Views/Match/MatchSummaryView.swift
  38. 18
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  39. 21
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  40. 40
      PadelClub/Views/Planning/SchedulerView.swift
  41. 4
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  42. 62
      PadelClub/Views/Round/LoserRoundView.swift
  43. 146
      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. 26
      PadelClub/Views/Tournament/FileImportView.swift
  48. 418
      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. 84
      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 */; }; FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; };
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; };
FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.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 */; }; FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FFCFBFFD2BBBE86600B82851 /* Algorithms */; };
FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; }; FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; };
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.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 */; }; FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; };
FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; };
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.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 */; }; FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; };
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -597,7 +596,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */,
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */, FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */,
FF92660D2C241CE0002361A4 /* Zip in Frameworks */, FF92660D2C241CE0002361A4 /* Zip in Frameworks */,
C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */, C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */,
@ -646,7 +644,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF92660F2C255E4A002361A4 /* PadelClub.entitlements */, FF92660F2C255E4A002361A4 /* PadelClub.entitlements */,
FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */,
FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */, FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */,
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */, FFA6D78A2BB0BEB3003A31F3 /* Info.plist */,
C4EC6F562BE92CAC000CEAB4 /* local.plist */, C4EC6F562BE92CAC000CEAB4 /* local.plist */,
@ -1259,6 +1256,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */, FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */,
FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1343,7 +1341,6 @@
C425D3FA2B6D249D002A7B48 /* Frameworks */, C425D3FA2B6D249D002A7B48 /* Frameworks */,
C425D3FB2B6D249D002A7B48 /* Resources */, C425D3FB2B6D249D002A7B48 /* Resources */,
FF2BE4892B85E27400592328 /* Embed Frameworks */, FF2BE4892B85E27400592328 /* Embed Frameworks */,
FFE2D2D62C216C1700D0C7BE /* ShellScript */,
); );
buildRules = ( buildRules = (
); );
@ -1352,7 +1349,6 @@
name = PadelClub; name = PadelClub;
packageProductDependencies = ( packageProductDependencies = (
FFCFBFFD2BBBE86600B82851 /* Algorithms */, FFCFBFFD2BBBE86600B82851 /* Algorithms */,
FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */,
FF92660C2C241CE0002361A4 /* Zip */, FF92660C2C241CE0002361A4 /* Zip */,
); );
productName = PadelClub; productName = PadelClub;
@ -1430,7 +1426,6 @@
mainGroup = C425D3F42B6D249D002A7B48; mainGroup = C425D3F42B6D249D002A7B48;
packageReferences = ( packageReferences = (
FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */, FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */, FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */,
); );
productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */; productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */;
@ -1460,7 +1455,6 @@
FF1F4B892BFA02A4000B4573 /* groupstagecol-template.html in Resources */, FF1F4B892BFA02A4000B4573 /* groupstagecol-template.html in Resources */,
FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */, FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */,
FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */, FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */,
FFE2D2D82C216D4800D0C7BE /* GoogleService-Info.plist in Resources */,
FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */, FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */,
FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */, FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */,
C45BAE3B2BC6DF10002EEC8A /* SyncedProducts.storekit in Resources */, C45BAE3B2BC6DF10002EEC8A /* SyncedProducts.storekit in Resources */,
@ -1486,31 +1480,6 @@
}; };
/* End PBXResourcesBuildPhase section */ /* 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 */ /* Begin PBXSourcesBuildPhase section */
C425D3F92B6D249D002A7B48 /* Sources */ = { C425D3F92B6D249D002A7B48 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
@ -1549,6 +1518,7 @@
FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */,
FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */, FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */,
C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */,
FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */,
FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */, FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */,
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */,
@ -1916,7 +1886,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67; CURRENT_PROJECT_VERSION = 76;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -1940,7 +1910,6 @@
); );
MARKETING_VERSION = 0.1; MARKETING_VERSION = 0.1;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
OTHER_LDFLAGS = "-ObjC";
OTHER_SWIFT_FLAGS = ""; OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1957,7 +1926,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67; CURRENT_PROJECT_VERSION = 76;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1980,7 +1949,6 @@
); );
MARKETING_VERSION = 0.1; MARKETING_VERSION = 0.1;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; 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"; 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_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -2124,14 +2092,6 @@
minimumVersion = 2.1.2; 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 */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -2145,11 +2105,6 @@
package = FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */; package = FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
productName = Algorithms; productName = Algorithms;
}; };
FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */ = {
isa = XCSwiftPackageProductDependency;
package = FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseCrashlytics;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */

@ -76,7 +76,7 @@ class Club : ModelObject, Storable, Hashable {
} }
var customizedCourts: [Court] { 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 { override func deleteDependencies() throws {
@ -231,10 +231,10 @@ extension Club {
identify a club : code, name, ?? 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 { if let club {
return clubs.first! return club
} else { } else {
return Club(creator: StoreCenter.main.userId, name: name, code: code, city: city, zipCode: zipCode) 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 // MARK: - Computed dependencies
var tournaments: [Tournament] { var tournaments: [Tournament] {
Store.main.filter { $0.event == self.id } DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false }
} }
func clubObject() -> Club? { func clubObject() -> Club? {
@ -47,7 +47,7 @@ class Event: ModelObject, Storable {
} }
var courtsUnavailability: [DateInterval] { var courtsUnavailability: [DateInterval] {
Store.main.filter(isIncluded: { $0.event == id }) DataStore.shared.dateIntervals.filter({ $0.event == id })
} }
// MARK: - // MARK: -

@ -200,7 +200,7 @@ class GroupStage: ModelObject, Storable {
} }
func availableToStart(playedMatches: [Match], in runningMatches: [Match]) async -> [Match] { func availableToStart(playedMatches: [Match], in runningMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -211,7 +211,7 @@ class GroupStage: ModelObject, Storable {
} }
func runningMatches(playedMatches: [Match]) -> [Match] { func runningMatches(playedMatches: [Match]) -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -222,7 +222,7 @@ class GroupStage: ModelObject, Storable {
} }
func asyncRunningMatches(playedMatches: [Match]) async -> [Match] { func asyncRunningMatches(playedMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -234,7 +234,7 @@ class GroupStage: ModelObject, Storable {
func readyMatches(playedMatches: [Match]) async -> [Match] { func readyMatches(playedMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -245,7 +245,7 @@ class GroupStage: ModelObject, Storable {
} }
func finishedMatches(playedMatches: [Match]) -> [Match] { func finishedMatches(playedMatches: [Match]) -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -332,7 +332,6 @@ class GroupStage: ModelObject, Storable {
func unsortedTeams() -> [TeamRegistration] { func unsortedTeams() -> [TeamRegistration] {
return self.tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } 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] { func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
@ -433,7 +432,7 @@ extension GroupStage {
} }
extension GroupStage: Selectable { extension GroupStage: Selectable {
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
groupStageTitle() groupStageTitle()
} }

@ -69,7 +69,6 @@ class Match: ModelObject, Storable {
var teamScores: [TeamScore] { var teamScores: [TeamScore] {
return self.tournamentStore.teamScores.filter { $0.match == self.id } return self.tournamentStore.teamScores.filter { $0.match == self.id }
// return Store.main.filter { $0.match == self.id }
} }
// MARK: - // MARK: -
@ -82,6 +81,13 @@ class Match: ModelObject, Storable {
} }
func indexInRound(in matches: [Match]? = nil) -> Int { 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 { if groupStage != nil {
return index return index
} else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) { } 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 { 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 { if let groupStageObject {
return groupStageObject.localizedMatchUpLabel(for: index) 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 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? { private func _forwardMatch(inRound round: Round) -> Match? {
guard let roundObjectNextRound = round.nextRound() else { return nil } guard let roundObjectNextRound = round.nextRound() else { return nil }
let nextIndex = (index - 1) / 2 let nextIndex = (index - 1) / 2
return self.tournamentStore.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }) 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) { func _toggleForwardMatchDisableState(_ state: Bool) {
@ -378,7 +388,6 @@ class Match: ModelObject, Storable {
func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? { func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? {
return self.tournamentStore.matches.first(where: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }) 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 { func getDuration() -> Int {
@ -405,26 +414,20 @@ class Match: ModelObject, Storable {
func topPreviousRoundMatch() -> Match? { func topPreviousRoundMatch() -> Match? {
guard let roundObject else { return nil } guard let roundObject else { return nil }
let matches: [Match] = self.tournamentStore.matches.filter { match in let topPreviousRoundMatchIndex = topPreviousRoundMatchIndex()
match.index == topPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id let roundObjectPreviousRoundId = roundObject.previousRound()?.id
} return self.tournamentStore.matches.first(where: { match in
return matches.sorted(by: \.index).first match.round != nil && match.round == roundObjectPreviousRoundId && match.index == topPreviousRoundMatchIndex
})
} }
func bottomPreviousRoundMatch() -> Match? { func bottomPreviousRoundMatch() -> Match? {
guard let roundObject else { return nil } guard let roundObject else { return nil }
let matches: [Match] = self.tournamentStore.matches.filter { match in let bottomPreviousRoundMatchIndex = bottomPreviousRoundMatchIndex()
match.index == bottomPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id let roundObjectPreviousRoundId = roundObject.previousRound()?.id
} return self.tournamentStore.matches.first(where: { match in
return matches.sorted(by: \.index).first match.round != nil && match.round == roundObjectPreviousRoundId && match.index == bottomPreviousRoundMatchIndex
} })
func upperBracketMatch(_ teamPosition: TeamPosition) -> Match? {
if teamPosition == .one {
return roundObject?.upperBracketTopMatch(ofMatchIndex: index)
} else {
return roundObject?.upperBracketBottomMatch(ofMatchIndex: index)
}
} }
func previousMatch(_ teamPosition: TeamPosition) -> Match? { 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 { var computedOrder: Int {
guard let roundObject else { return index } guard let roundObject else { return index }
return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound() return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound()
@ -447,9 +445,9 @@ class Match: ModelObject, Storable {
func previousMatches() -> [Match] { func previousMatches() -> [Match] {
guard let roundObject else { return [] } guard let roundObject else { return [] }
let roundObjectPreviousRoundId = roundObject.previousRound()?.id
return self.tournamentStore.matches.filter { match in return self.tournamentStore.matches.filter { match in
(match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex()) match.round == roundObjectPreviousRoundId && (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex())
&& match.round == roundObject.previousRound()?.id
}.sorted(by: \.index) }.sorted(by: \.index)
} }
@ -683,10 +681,22 @@ class Match: ModelObject, Storable {
} }
func teams() -> [TeamRegistration] { 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 { if groupStage != nil {
return [groupStageProjectedTeam(.one), groupStageProjectedTeam(.two)].compactMap { $0 } 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)? { func scoreDifference(_ teamPosition: Int) -> (set: Int, game: Int)? {
@ -715,7 +725,8 @@ class Match: ModelObject, Storable {
func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? { func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
guard let roundObject else { return nil } 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 { func teamWon(_ team: TeamRegistration?) -> Bool {
@ -729,7 +740,7 @@ class Match: ModelObject, Storable {
} }
func team(_ team: TeamPosition) -> TeamRegistration? { func team(_ team: TeamPosition) -> TeamRegistration? {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -737,19 +748,9 @@ class Match: ModelObject, Storable {
} }
#endif #endif
if groupStage != nil { if groupStage != nil {
switch team { return groupStageProjectedTeam(team)
case .one:
return groupStageProjectedTeam(.one)
case .two:
return groupStageProjectedTeam(.two)
}
} else { } else {
switch team { return roundProjectedTeam(team)
case .one:
return roundProjectedTeam(.one)
case .two:
return roundProjectedTeam(.two)
}
} }
} }

@ -42,7 +42,7 @@ class MonthData : ModelObject, Storable {
let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: mostRecentDateAvailable) let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: mostRecentDateAvailable)
await MainActor.run { await MainActor.run {
if let lastDataSource = DataStore.shared.appSettings.lastDataSource { 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.maleUnrankedValue = lastDataSourceMaleUnranked?.0
currentMonthData.maleCount = lastDataSourceMaleUnranked?.1 currentMonthData.maleCount = lastDataSourceMaleUnranked?.1
currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0 currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0

@ -81,8 +81,15 @@ class PlayerRegistration: ModelObject, Storable {
licenceId = federalData[3] licenceId = federalData[3]
clubName = federalData[4] clubName = federalData[4]
rank = Int(federalData[5]) rank = Int(federalData[5])
email = federalData[6] let _email = federalData[6]
phoneNumber = federalData[7] if _email.isEmpty == false {
self.email = _email
}
let _phoneNumber = federalData[7]
if _phoneNumber.isEmpty == false {
self.phoneNumber = _phoneNumber
}
source = .beachPadel source = .beachPadel
if sexUnknown { if sexUnknown {
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {

@ -41,7 +41,7 @@ class Round: ModelObject, Storable {
} }
func _matches() -> [Match] { 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 } // return Store.main.filter { $0.round == self.id }
} }
@ -66,7 +66,11 @@ class Round: ModelObject, Storable {
} }
func hasEnded() -> Bool { func hasEnded() -> Bool {
return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false if parent == nil {
return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false
} else {
return enabledMatches().anySatisfy({ $0.hasEnded() == false }) == false
}
} }
func upperMatches(ofMatch match: Match) -> [Match] { func upperMatches(ofMatch match: Match) -> [Match] {
@ -81,8 +85,8 @@ class Round: ModelObject, Storable {
func previousMatches(ofMatch match: Match) -> [Match] { func previousMatches(ofMatch match: Match) -> [Match] {
guard let previousRound = previousRound() else { return [] } guard let previousRound = previousRound() else { return [] }
return self.tournamentStore.filter { return self.tournamentStore.matches.filter {
($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) && $0.round == previousRound.id $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex())
} }
// return Store.main.filter { // return Store.main.filter {
@ -103,13 +107,8 @@ class Round: ModelObject, Storable {
} }
} }
func team(_ team: TeamPosition, inMatch match: Match) -> TeamRegistration? { func team(_ team: TeamPosition, inMatch match: Match, previousRound: Round?) -> TeamRegistration? {
switch team { return roundProjectedTeam(team, inMatch: match, previousRound: previousRound)
case .one:
return roundProjectedTeam(.one, inMatch: match)
case .two:
return roundProjectedTeam(.two, inMatch: match)
}
} }
func seed(_ team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { func seed(_ team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
@ -119,17 +118,11 @@ class Round: ModelObject, Storable {
&& ($0.bracketPosition! / 2) == matchIndex && ($0.bracketPosition! / 2) == matchIndex
&& ($0.bracketPosition! % 2) == team.rawValue && ($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] { func seeds(inMatchIndex matchIndex: Int) -> [TeamRegistration] {
return self.tournamentStore.teamRegistrations.filter { return self.tournamentStore.teamRegistrations.filter {
$0.tournament == tournament $0.tournament == tournament
&& $0.bracketPosition != nil && $0.bracketPosition != nil
&& ($0.bracketPosition! / 2) == matchIndex && ($0.bracketPosition! / 2) == matchIndex
@ -162,7 +155,14 @@ class Round: ModelObject, Storable {
return playedMatches().flatMap({ $0.teams() }) 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) { if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) {
return seed return seed
} }
@ -171,86 +171,116 @@ class Round: ModelObject, Storable {
case .one: case .one:
if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 }) { if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 }) {
return luckyLoser.team return luckyLoser.team
} else if let parent = upperBracketTopMatch(ofMatchIndex: match.index)?.losingTeamId { } else if let previousMatch = topPreviousRoundMatch(ofMatch: match, previousRound: previousRound) {
return self.tournamentStore.teamRegistrations.findById(parent)
} else if let previousMatch = topPreviousRoundMatch(ofMatch: match) {
if let teamId = previousMatch.winningTeamId { if let teamId = previousMatch.winningTeamId {
return self.tournamentStore.teamRegistrations.findById(teamId) return self.tournamentStore.teamRegistrations.findById(teamId)
} else if previousMatch.disabled { } else if previousMatch.disabled {
return previousMatch.teams().first return previousMatch.teams().first
} }
} else if let parent = upperBracketTopMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId {
return Store.main.findById(parent)
} }
case .two: case .two:
if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) { if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) {
return luckyLoser.team return luckyLoser.team
} else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index)?.losingTeamId { } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) {
return self.tournamentStore.teamRegistrations.findById(parent)
} else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match) {
if let teamId = previousMatch.winningTeamId { if let teamId = previousMatch.winningTeamId {
return self.tournamentStore.teamRegistrations.findById(teamId) return self.tournamentStore.teamRegistrations.findById(teamId)
} else if previousMatch.disabled { } else if previousMatch.disabled {
return previousMatch.teams().first return previousMatch.teams().first
} }
} else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId {
return Store.main.findById(parent)
} }
} }
return nil 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) 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 upperBracketTopMatch
} }
return nil 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) 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 upperBracketBottomMatch
} }
return nil return nil
} }
func topPreviousRoundMatch(ofMatch match: Match) -> Match? {
guard let previousRound = previousRound() else { return nil } func topPreviousRoundMatch(ofMatch match: Match, previousRound: Round?) -> Match? {
#if DEBUG_TIME //DEBUGING TIME
let matches: [Match] = self.tournamentStore.matches.filter { let start = Date()
$0.index == match.topPreviousRoundMatchIndex() && $0.round == previousRound.id defer {
} let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
// let matches: [Match] = Store.main.filter { print("func topPreviousRoundMatch", match.id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
// $0.index == match.topPreviousRoundMatchIndex() && $0.round == previousRound.id }
// } #endif
return matches.sorted(by: \.index).first
guard let previousRound else { return nil }
let topPreviousRoundMatchIndex = match.topPreviousRoundMatchIndex()
return self.tournamentStore.matches.first(where: {
$0.round == previousRound.id && $0.index == topPreviousRoundMatchIndex
})
} }
func bottomPreviousRoundMatch(ofMatch match: Match) -> Match? { func bottomPreviousRoundMatch(ofMatch match: Match, previousRound: Round?) -> Match? {
guard let previousRound = previousRound() else { return nil } #if DEBUG_TIME //DEBUGING TIME
let matches: [Match] = self.tournamentStore.matches.filter { let start = Date()
$0.index == match.bottomPreviousRoundMatchIndex() && $0.round == previousRound.id defer {
} let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
return matches.sorted(by: \.index).first 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? { func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? {
return self.tournamentStore.matches.first(where: { self.tournamentStore.matches.first(where: {
let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index)
return $0.round == id && index == matchIndexInRound 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] { func enabledMatches() -> [Match] {
return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false } return self.tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index)
// return Store.main.filter { $0.round == self.id && $0.disabled == false }
} }
func displayableMatches() -> [Match] { 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() { if index == 0 && isUpperBracket() {
var matches : [Match?] = [playedMatches().first] var matches : [Match?] = [playedMatches().first]
matches.append(loserRounds().first?.playedMatches().first) matches.append(loserRounds().first?.playedMatches().first)
@ -269,18 +299,28 @@ class Round: ModelObject, Storable {
} }
func previousRound() -> Round? { func previousRound() -> Round? {
return self.tournamentStore.rounds.first(where: { $0.tournament == tournament && $0.parent == parent && $0.index == index + 1 }) #if DEBUG_TIME //DEBUGING TIME
// return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.parent == parent && $0.index == index + 1 }).first 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? { 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] { func loserRounds(forRoundIndex roundIndex: Int) -> [Round] {
return loserRoundsAndChildren().filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount) 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 { func isDisabled() -> Bool {
return _matches().allSatisfy({ $0.disabled }) return _matches().allSatisfy({ $0.disabled })
} }
@ -387,11 +427,16 @@ class Round: ModelObject, Storable {
func correspondingLoserRoundTitle(_ displayStyle: DisplayStyle = .wide) -> String { 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 initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index)
let seedsAfterThisRound: [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { let seedsAfterThisRound: [TeamRegistration] = self.tournamentStore.teamRegistrations.filter {
$0.tournament == tournament $0.bracketPosition != nil
&& $0.bracketPosition != nil
&& ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex
} }
@ -406,26 +451,40 @@ class Round: ModelObject, Storable {
} }
func hasNextRound() -> Bool { func hasNextRound() -> Bool {
return nextRound()?.isDisabled() == false return nextRound()?.isRankDisabled() == false
} }
func seedInterval() -> SeedInterval? { func seedInterval(expanded: Bool = false) -> SeedInterval? {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func seedInterval(expanded: Bool = false)", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
if parent == nil { 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 initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index)
let seedsAfterThisRound : [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { let seedsAfterThisRound : [TeamRegistration] = self.tournamentStore.teamRegistrations.filter {
$0.tournament == tournament $0.bracketPosition != nil
&& $0.bracketPosition != nil
&& ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex
} }
let playedMatches = playedMatches() let playedMatches = playedMatches()
let reduce = numberOfMatches / 2 - (playedMatches.count + seedsAfterThisRound.count) let seedInterval = SeedInterval(first: playedMatches.count + seedsAfterThisRound.count + 1, last: playedMatches.count * 2 + seedsAfterThisRound.count)
return SeedInterval(first: 1, last: numberOfMatches, reduce: reduce) return seedInterval
} }
if let previousRound = previousRound() { if let previousRound = previousRound() {
return previousRound.seedInterval()?.chunks()?.first if previousRound.enabledMatches().isEmpty == false && expanded == false {
return previousRound.seedInterval()?.chunks()?.first
} else {
return previousRound.previousRound()?.seedInterval()
}
} else if let parentRound { } else if let parentRound {
if parentRound.parent == nil && expanded == false {
return parentRound.seedInterval()
}
return parentRound.seedInterval()?.chunks()?.last return parentRound.seedInterval()?.chunks()?.last
} }
@ -462,8 +521,16 @@ class Round: ModelObject, Storable {
} }
func loserRounds() -> [Round] { 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] { func loserRoundsAndChildren() -> [Round] {
@ -604,7 +671,7 @@ extension Round: Selectable, Equatable {
} }
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
if let parentRound { if let parentRound {
return "Tour #\(parentRound.loserRounds().count - index)" return "Tour #\(parentRound.loserRounds().count - index)"
} else { } else {
@ -613,6 +680,15 @@ extension Round: Selectable, Equatable {
} }
func badgeValue() -> Int? { 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 { if let parentRound {
return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count
} else { } else {
@ -625,6 +701,13 @@ extension Round: Selectable, Equatable {
} }
func badgeImage() -> Badge? { 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] { func unsortedPlayers() -> [PlayerRegistration] {
return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id } return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id }
// Store.main.filter { $0.teamRegistration == self.id }
} }
// MARK: - // MARK: -
@ -144,23 +143,19 @@ class TeamRegistration: ModelObject, Storable {
} }
func teamScores() -> [TeamScore] { func teamScores() -> [TeamScore] {
return self.tournamentStore.teamScores.filter { $0.teamRegistration == id } return self.tournamentStore.teamScores.filter({ $0.teamRegistration == id })
// return Store.main.filter(isIncluded: { $0.teamRegistration == id })
} }
func wins() -> [Match] { func wins() -> [Match] {
return self.tournamentStore.matches.filter { $0.winningTeamId == id } return self.tournamentStore.matches.filter({ $0.winningTeamId == id })
// return Store.main.filter(isIncluded: { $0.winningTeamId == id })
} }
func loses() -> [Match] { func loses() -> [Match] {
return self.tournamentStore.matches.filter { $0.losingTeamId == id } return self.tournamentStore.matches.filter({ $0.losingTeamId == id })
// return Store.main.filter(isIncluded: { $0.losingTeamId == id })
} }
func matches() -> [Match] { func matches() -> [Match] {
return self.tournamentStore.matches.filter { $0.losingTeamId == id || $0.winningTeamId == id } return self.tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id })
// return Store.main.filter(isIncluded: { $0.losingTeamId == id || $0.winningTeamId == id })
} }
var tournamentCategory: TournamentCategory { var tournamentCategory: TournamentCategory {
@ -333,10 +328,8 @@ class TeamRegistration: ModelObject, Storable {
typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool
func players() -> [PlayerRegistration] { func players() -> [PlayerRegistration] {
let playerRegistration = self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id } self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in
return playerRegistration.sorted { (lhs, rhs) in
let predicates: [AreInIncreasingOrder] = [ let predicates: [AreInIncreasingOrder] = [
{ $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 }, { $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 },
{ $0.rank ?? 0 < $1.rank ?? 0 }, { $0.rank ?? 0 < $1.rank ?? 0 },
@ -391,14 +384,12 @@ class TeamRegistration: ModelObject, Storable {
guard let bracketPosition else { return nil } guard let bracketPosition else { return nil }
let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2)
return self.tournamentStore.rounds.first(where: { $0.index == roundIndex }) return self.tournamentStore.rounds.first(where: { $0.index == roundIndex })
// return Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == roundIndex }).first
} }
func initialMatch() -> Match? { func initialMatch() -> Match? {
guard let bracketPosition else { return nil } guard let bracketPosition else { return nil }
guard let initialRoundObject = initialRound() 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 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] { 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] = 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() return Set(runningMatches.compactMap { $0.courtIndex }).sorted()
} }
@ -553,12 +561,7 @@ class Tournament : ModelObject, Storable {
} }
func groupStageSpots() -> Int { func groupStageSpots() -> Int {
let _groupStagesCount = groupStages().count return groupStages().map { $0.size }.reduce(0,+)
if groupStageCount != _groupStagesCount {
return groupStageCount * teamsPerGroupStage
} else {
return groupStages().map { $0.size }.reduce(0,+)
}
} }
func seeds() -> [TeamRegistration] { func seeds() -> [TeamRegistration] {
@ -568,6 +571,15 @@ class Tournament : ModelObject, Storable {
} }
func availableSeeds() -> [TeamRegistration] { 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() } return seeds().filter { $0.isSeedable() }
} }
@ -758,19 +770,16 @@ class Tournament : ModelObject, Storable {
} }
func allMatches() -> [Match] { func allMatches() -> [Match] {
let unsortedGroupStages: [GroupStage] = Store.main.filter { $0.tournament == self.id } return self.tournamentStore.matches.filter { $0.disabled == false }
let matches: [Match] = self.allGroupStages().flatMap { $0._matches() } + allRoundMatches()
return matches.filter({ $0.disabled == false })
} }
func _allMatchesIncludingDisabled() -> [Match] { func _allMatchesIncludingDisabled() -> [Match] {
return self.allGroupStages().flatMap { $0._matches() } + allRounds().flatMap { $0._matches() } return Array(self.tournamentStore.matches)
} }
func rounds() -> [Round] { func rounds() -> [Round] {
let rounds: [Round] = self.tournamentStore.rounds.filter { $0.parent == nil } let rounds: [Round] = self.tournamentStore.rounds.filter { $0.parent == nil }
return rounds.sorted(by: \.index).reversed() return rounds.sorted(by: \.index).reversed()
// Store.main.filter { $0.tournament == self.id && $0.parent == nil }.sorted(by: \.index).reversed()
} }
func sortedTeams() -> [TeamRegistration] { func sortedTeams() -> [TeamRegistration] {
@ -779,7 +788,7 @@ class Tournament : ModelObject, Storable {
} }
func selectedSortedTeams() -> [TeamRegistration] { func selectedSortedTeams() -> [TeamRegistration] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -876,7 +885,7 @@ class Tournament : ModelObject, Storable {
} }
func unsortedPlayers() -> [PlayerRegistration] { func unsortedPlayers() -> [PlayerRegistration] {
return self.unsortedTeams().flatMap { $0.unsortedPlayers() } return Array(self.tournamentStore.playerRegistrations)
} }
func selectedPlayers() -> [PlayerRegistration] { func selectedPlayers() -> [PlayerRegistration] {
@ -892,7 +901,7 @@ class Tournament : ModelObject, Storable {
} }
func players() -> [PlayerRegistration] { 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? { func unrankValue(for malePlayer: Bool) -> Int? {
@ -1029,13 +1038,12 @@ class Tournament : ModelObject, Storable {
} }
func groupStagesMatches() -> [Match] { func groupStagesMatches() -> [Match] {
let groupStageIds = groupStages().map { $0.id } return self.tournamentStore.matches.filter { $0.groupStage != nil }
return self.tournamentStore.matches.filter { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }
// return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) // return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
} }
func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] { func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -1046,7 +1054,7 @@ class Tournament : ModelObject, Storable {
} }
func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] { func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -1057,7 +1065,7 @@ class Tournament : ModelObject, Storable {
} }
func runningMatches(_ allMatches: [Match]) -> [Match] { func runningMatches(_ allMatches: [Match]) -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -1068,7 +1076,7 @@ class Tournament : ModelObject, Storable {
} }
func readyMatches(_ allMatches: [Match]) async -> [Match] { func readyMatches(_ allMatches: [Match]) async -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) 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] { func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -1110,22 +1118,45 @@ class Tournament : ModelObject, Storable {
} }
let others: [Round] = rounds.flatMap { round in 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 }) }.compactMap({ $0 })
others.forEach { round in others.forEach { round in
print("round", round.roundTitle())
if let interval = round.seedInterval() { if let interval = round.seedInterval() {
print("interval", interval.localizedLabel())
let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() } 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 }) 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 }) let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false })
print("losers", losers.count)
if winners.isEmpty { if winners.isEmpty {
let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) 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) } disabledIds.forEach { ids.insert($0) }
} else { } 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) } 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) } 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: " - ") 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 { func hideWeight() -> Bool {
return tournamentLevel.hideWeight() return tournamentLevel.hideWeight()
} }
@ -1261,6 +1301,14 @@ class Tournament : ModelObject, Storable {
func availableQualifiedTeams() -> [TeamRegistration] { 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 }) return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil })
} }
@ -1362,20 +1410,28 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel) 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 availableSeeds = availableSeeds()
let cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last) var description: String? = nil
if availableSeeds.isEmpty == false { if availableSeeds.isEmpty == false {
return ("placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série", cut) description = "placer \(availableSeeds.count) équipe\(availableSeeds.count.pluralSuffix)"
} }
let availableQualifiedTeams = availableQualifiedTeams() if description == nil {
if availableQualifiedTeams.isEmpty == false { let availableQualifiedTeams = availableQualifiedTeams()
return ("placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix, cut) if availableQualifiedTeams.isEmpty == false {
description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix
}
}
var cut: TeamRegistration.TeamRange? = nil
if description == nil && isAnimation() == false {
cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last)
} }
if let round = getActiveRound() { 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 { } else {
return ("à construire", nil) return ("", description, nil)
} }
} }
@ -1383,10 +1439,10 @@ class Tournament : ModelObject, Storable {
let groupStageTeams = groupStageTeams() let groupStageTeams = groupStageTeams()
let groupStageTeamsCount = groupStageTeams.count let groupStageTeamsCount = groupStageTeams.count
if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() { 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() }) let runningGroupStages = groupStages().filter({ $0.isRunning() })
if groupStagesAreOver() { return ("terminées", cut) } if groupStagesAreOver() { return ("terminées", cut) }
@ -1403,16 +1459,12 @@ class Tournament : ModelObject, Storable {
} }
func settingsDescriptionLocalizedLabel() -> String { 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 { func structureDescriptionLocalizedLabel() -> String {
if state() == .initial { let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil
return "à valider" return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
} else {
let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil
return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
}
} }
func deleteAndBuildEverything() { func deleteAndBuildEverything() {
@ -1490,6 +1542,7 @@ class Tournament : ModelObject, Storable {
if let round: Round = self.getRound(atRoundIndex: roundIndex) { if let round: Round = self.getRound(atRoundIndex: roundIndex) {
return self.tournamentStore.matches.first(where: { $0.round == round.id && $0.index == matchIndex }) 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 Store.main.filter(isIncluded: { $0.round == round.id && $0.index == matchIndex }).first
} }
return nil return nil
} }
@ -1754,7 +1807,6 @@ class Tournament : ModelObject, Storable {
guard let rankSourceDate else { return nil } guard let rankSourceDate else { return nil }
let dateString = URL.importDateFormatter.string(from: rankSourceDate) let dateString = URL.importDateFormatter.string(from: rankSourceDate)
return DataStore.shared.monthData.first(where: { $0.monthKey == dateString }) return DataStore.shared.monthData.first(where: { $0.monthKey == dateString })
// return Store.main.filter(isIncluded: { $0.monthKey == dateString }).first
} }
var maleUnrankedValue: Int? { 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: - // MARK: -
func insertOnServer() throws { func insertOnServer() throws {

@ -87,11 +87,11 @@ class User: ModelObject, UserBase, Storable {
} }
func clubsObjects(includeCreated: Bool = false) -> [Club] { 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] { 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) { func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) {

@ -195,3 +195,9 @@ extension String {
return url 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 SwiftUI
import LeStorage import LeStorage
import TipKit import TipKit
import FirebaseCore
@main @main
struct PadelClubApp: App { struct PadelClubApp: App {
@ -16,7 +15,6 @@ struct PadelClubApp: App {
@State private var navigationViewModel = NavigationViewModel() @State private var navigationViewModel = NavigationViewModel()
@StateObject var networkMonitor: NetworkMonitor = NetworkMonitor() @StateObject var networkMonitor: NetworkMonitor = NetworkMonitor()
@StateObject var dataStore = DataStore.shared @StateObject var dataStore = DataStore.shared
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@State private var registrationError: RegistrationError? = nil @State private var registrationError: RegistrationError? = nil
var presentError: Binding<Bool> { 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 var teamName: String? = nil
let players = team.map { player in let players = team.map { player in
let data = player.components(separatedBy: separator) let data = player.components(separatedBy: separator)
let firstName : String = data[safe: 2]?.trimmed ?? "" let lastName : String = data[safe: 2]?.trimmed ?? ""
let lastName : String = data[safe: 3]?.trimmed ?? "" let firstName : String = data[safe: 3]?.trimmed ?? ""
let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male
if data[safe: 1]?.trimmed != nil { if data[safe: 1]?.trimmed != nil {
teamName = data[safe: 1]?.trimmed teamName = data[safe: 1]?.trimmed
} }
let phoneNumber : String? = data[safe: 4]?.trimmed let phoneNumber : String? = data[safe: 4]?.trimmed
let email : String? = data[safe: 5]?.trimmed let email : String? = data[safe: 5]?.trimmed
//let level : String? = data[safe: 6]?.trimmed let rank : Int? = data[safe: 6]?.trimmed.toInt()
let player = PlayerRegistration(firstName: firstName, lastName: lastName, sex: sex, phoneNumber: phoneNumber, email: email) 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 return player
} }

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

@ -28,7 +28,7 @@ enum PageLink: String, Identifiable, CaseIterable {
case teams = "Équipes" case teams = "Équipes"
case summons = "Convocations" case summons = "Convocations"
case groupStages = "Poules" case groupStages = "Poules"
case matches = "Matchs" case matches = "Tournoi"
case rankings = "Classement" case rankings = "Classement"
case broadcast = "Mode TV (Tournoi)" case broadcast = "Mode TV (Tournoi)"
case clubBroadcast = "Mode TV (Club)" 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 localizedTitleKey
} }

@ -25,7 +25,7 @@ class FederalDataViewModel {
labels.append(contentsOf: categories.map { $0.localizedLabel() }) labels.append(contentsOf: categories.map { $0.localizedLabel() })
labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) labels.append(contentsOf: ageCategories.map { $0.localizedLabel() })
let clubNames = selectedClubs.compactMap { codeClub in 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) return club?.clubTitle(.short)
} }
@ -35,7 +35,7 @@ class FederalDataViewModel {
func selectedClub() -> Club? { func selectedClub() -> Club? {
if selectedClubs.isEmpty == false { 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 { } else {
return nil return nil
} }

@ -25,7 +25,7 @@ struct SeedInterval: Hashable, Comparable {
func isFixed() -> Bool { func isFixed() -> Bool {
first == 1 && last == 2 first == 1 && last == 2
} }
var count: Int { var count: Int {
dimension dimension
} }
@ -44,7 +44,15 @@ struct SeedInterval: Hashable, Comparable {
return nil return nil
} }
} }
var computedLast: Int {
last - reduce
}
var computedFirst: Int {
first - reduce
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if dimension < 2 { if dimension < 2 {
return "#\(first - reduce) / #\(last - reduce)" return "#\(first - reduce) / #\(last - reduce)"

@ -9,7 +9,7 @@ import Foundation
import SwiftUI import SwiftUI
protocol Selectable { protocol Selectable {
func selectionLabel() -> String func selectionLabel(index: Int) -> String
func badgeValue() -> Int? func badgeValue() -> Int?
func badgeImage() -> Badge? func badgeImage() -> Badge?
func badgeValueColor() -> Color? func badgeValueColor() -> Color?

@ -51,7 +51,7 @@ struct CallSettingsView: View {
Logger.error(error) Logger.error(error)
} }
} label: { } 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 { var body: some View {
let groupStages = tournament.groupStages() let groupStages = tournament.groupStages()
List { List {
PlayersWithoutContactView(players: groupStages.flatMap({ $0.unsortedTeams() }).flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank))
_sameTimeGroupStageView(groupStages: groupStages) _sameTimeGroupStageView(groupStages: groupStages)
ForEach(groupStages) { groupStage in ForEach(groupStages) { groupStage in

@ -9,11 +9,14 @@ import SwiftUI
struct SeedsCallingView: View { struct SeedsCallingView: View {
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@State private var displayByMatch: Bool = false @State private var displayByMatch: Bool = true
var body: some View { var body: some View {
List { 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 seeds = round.seeds()
let callSeeds = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) let callSeeds = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false })
if seeds.isEmpty == false { if seeds.isEmpty == false {
@ -45,6 +48,14 @@ struct SeedsCallingView: View {
} }
let keys = times.keys.compactMap { $0 }.sorted() let keys = times.keys.compactMap { $0 }.sorted()
List { List {
Section {
RowButtonView(displayByMatch ? "Regrouper par horaire" : "Lister par match") {
displayByMatch.toggle()
}
}
if displayByMatch == false { if displayByMatch == false {
ForEach(keys, id: \.self) { time in ForEach(keys, id: \.self) { time in
if let matches = times[time] { if let matches = times[time] {
@ -106,14 +117,6 @@ struct SeedsCallingView: View {
.navigationTitle(round.roundTitle()) .navigationTitle(round.roundTitle())
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .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 @State private var sentError: ContactManagerError? = nil
let addLink: Bool let addLink: Bool
// @State var cannotPayForTournament: Bool = false // @State var cannotPayForTournament: Bool = false
@State private var pageLink: PageLink = .teams @State private var pageLink: PageLink = .matches
@State var showSubscriptionView: Bool = false @State var showSubscriptionView: Bool = false
@State var showUserCreationView: Bool = false @State var showUserCreationView: Bool = false
@ -81,6 +81,8 @@ struct SendToAllView: View {
.tag(round.id) .tag(round.id)
} }
} }
} footer: {
Text("Si vous ne souhaitez pas contacter toutes les équipes, choisissez un ou plusieurs groupes d'équipes manuellement.")
} }
if addLink { if addLink {
@ -88,10 +90,10 @@ struct SendToAllView: View {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
Picker(selection: $pageLink) { Picker(selection: $pageLink) {
ForEach(links) { pageLink in ForEach(links) { pageLink in
Text(pageLink.localizedLabel()) Text(pageLink.localizedLabel()).tag(pageLink)
} }
} label: { } label: {
Text("Choisir une page du tournoi en particulier") Text("Lien à partager")
} }
.pickerStyle(.menu) .pickerStyle(.menu)
} }

@ -130,7 +130,8 @@ struct EventCreationView: View {
private func _validate() { private func _validate() {
let event = Event(creator: StoreCenter.main.userId, name: eventName) let event = Event(creator: StoreCenter.main.userId, name: eventName)
event.club = selectedClub?.id
do { do {
try dataStore.events.addOrUpdate(instance: event) try dataStore.events.addOrUpdate(instance: event)
} catch { } catch {
@ -152,15 +153,15 @@ struct EventCreationView: View {
} }
if let selectedClub, let verifiedSelectedClubId = dataStore.clubs.first(where: { selectedClub.id == $0.id })?.id { // if let selectedClub, let verifiedSelectedClubId = dataStore.clubs.first(where: { selectedClub.id == $0.id })?.id {
event.club = verifiedSelectedClubId // event.club = verifiedSelectedClubId
do { // do {
try dataStore.events.addOrUpdate(instance: event) // try dataStore.events.addOrUpdate(instance: event)
} catch { // } catch {
Logger.error(error) // Logger.error(error)
} // }
} // }
//
dismiss() dismiss()
navigation.path.append(tournaments.first!) navigation.path.append(tournaments.first!)

@ -21,7 +21,7 @@ enum EventDestination: Identifiable, Selectable, Equatable {
return String(describing: self) return String(describing: self)
} }
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
switch self { switch self {
case .links: case .links:
return "Liens" return "Liens"

@ -30,7 +30,7 @@ struct ClubsView: View {
#if DEBUG #if DEBUG
Section { Section {
RowButtonView("Delete unexisted clubs", action: { RowButtonView("Delete unexisted clubs", action: {
var ids = dataStore.user.clubs let ids = dataStore.user.clubs
ids.forEach { clubId in ids.forEach { clubId in
if dataStore.clubs.findById(clubId) == nil { if dataStore.clubs.findById(clubId) == nil {
dataStore.user.clubs.removeAll(where: { $0 == clubId }) dataStore.user.clubs.removeAll(where: { $0 == clubId })

@ -33,11 +33,12 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
.id("settings") .id("settings")
} }
ForEach(destinations) { destination in ForEach(destinations.indices, id: \.self) { index in
let destination = destinations[index]
Button { Button {
selectedDestination = destination selectedDestination = destination
} label: { } label: {
Text(destination.selectionLabel()) Text(destination.selectionLabel(index: index))
.foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black)
} }
.padding() .padding()

@ -23,6 +23,44 @@ struct GroupStageSettingsView: View {
if tournament.shouldVerifyGroupStage { if tournament.shouldVerifyGroupStage {
Section { 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) { RowButtonView("Valider les poules en l'état", role: .destructive) {
tournament.shouldVerifyGroupStage = false tournament.shouldVerifyGroupStage = false
do { do {

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

@ -39,7 +39,7 @@ struct GroupStageTeamReplacementView: View {
private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String { private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String {
let left = teamRange.left?.weight let left = teamRange.left?.weight
let right = teamRange.right?.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: ",") return sides.map({ String($0) }).joined(separator: ",")
} }
@ -73,9 +73,11 @@ struct GroupStageTeamReplacementView: View {
.labelsHidden() .labelsHidden()
.pickerStyle(.inline) .pickerStyle(.inline)
} header: { } header: {
Text("Remplacer") if let selectedPlayer {
} footer: { Text("Remplacer \(selectedPlayer.playerLabel())")
Text("Remplacement de l'équipe ou d'un joueur particulier") } else {
Text("Remplacer toute l'équipe")
}
} }
if let teamRange { if let teamRange {
@ -147,7 +149,7 @@ struct GroupStageTeamReplacementView: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if let team { if let team, team.weight + playerWeight > 0 {
Text((team.weight + playerWeight).formatted()).font(.largeTitle) Text((team.weight + playerWeight).formatted()).font(.largeTitle)
} else { } else {
Text("Aucune limite") Text("Aucune limite")

@ -14,6 +14,7 @@ struct PlayerBlockView: View {
let color: Color let color: Color
let width: CGFloat let width: CGFloat
let teamScore: TeamScore? let teamScore: TeamScore?
let isWalkOut: Bool
init(match: Match, teamPosition: TeamPosition, color: Color, width: CGFloat) { init(match: Match, teamPosition: TeamPosition, color: Color, width: CGFloat) {
self.match = match self.match = match
@ -22,7 +23,9 @@ struct PlayerBlockView: View {
self.team = theTeam self.team = theTeam
self.color = color self.color = color
self.width = width 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]? { var names: [String]? {
@ -36,11 +39,7 @@ struct PlayerBlockView: View {
var hideScore: Bool { var hideScore: Bool {
match.hasWalkoutTeam() match.hasWalkoutTeam()
} }
var isWalkOut: Bool {
match.teamWalkOut(team)
}
var scores: [String] { var scores: [String] {
teamScore?.score?.components(separatedBy: ",") ?? [] teamScore?.score?.components(separatedBy: ",") ?? []
} }

@ -100,11 +100,9 @@ struct MatchDetailView: View {
} }
} }
if match.isReady() { Section {
Section { RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
RowButtonView("Saisir les résultats", systemImage: "list.clipboard") { self._editScores()
self._editScores()
}
} }
} }
@ -240,6 +238,15 @@ struct MatchDetailView: View {
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Menu { 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 { if match.courtIndex != nil {
Button(role: .destructive) { Button(role: .destructive) {
match.removeCourt() match.removeCourt()
@ -440,7 +447,15 @@ struct MatchDetailView: View {
} }
fileprivate func _editScores() { 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._verifyUser {
self._payTournamentAndExecute { self._payTournamentAndExecute {
self.scoreType = .edition self.scoreType = .edition

@ -12,12 +12,13 @@ struct MatchRowView: View {
var match: Match var match: Match
var tournament: Tournament var tournament: Tournament
let matchViewStyle: MatchViewStyle let matchViewStyle: MatchViewStyle
var title: String? = nil
@Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed @Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed
@ViewBuilder @ViewBuilder
var body: some View { 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) MatchSetupView(match: match)
} else { } else {
// MatchSummaryView(match: match, matchViewStyle: matchViewStyle) // MatchSummaryView(match: match, matchViewStyle: matchViewStyle)
@ -60,7 +61,7 @@ struct MatchRowView: View {
MatchDetailView(match: match, matchViewStyle: matchViewStyle) MatchDetailView(match: match, matchViewStyle: matchViewStyle)
.environment(self.tournament) .environment(self.tournament)
} label: { } label: {
MatchSummaryView(match: match, matchViewStyle: matchViewStyle) MatchSummaryView(match: match, matchViewStyle: matchViewStyle, title: title)
} }
//.modifier(BroadcastViewModifier(isBroadcasted: match.isBroadcasted())) //.modifier(BroadcastViewModifier(isBroadcasted: match.isBroadcasted()))
} }

@ -18,7 +18,7 @@ struct MatchSummaryView: View {
let color: Color let color: Color
let width: CGFloat let width: CGFloat
init(match: Match, matchViewStyle: MatchViewStyle) { init(match: Match, matchViewStyle: MatchViewStyle, title: String? = nil) {
self.match = match self.match = match
self.matchViewStyle = matchViewStyle self.matchViewStyle = matchViewStyle
self.padding = matchViewStyle == .plainStyle ? 0 : 8 self.padding = matchViewStyle == .plainStyle ? 0 : 8
@ -34,9 +34,9 @@ struct MatchSummaryView: View {
self.roundTitle = nil 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 self.courtName = court
} else { } else {
self.courtName = nil self.courtName = nil
@ -54,7 +54,9 @@ struct MatchSummaryView: View {
if let roundTitle { if let roundTitle {
Text(roundTitle).fontWeight(.semibold) Text(roundTitle).fontWeight(.semibold)
} }
Text(matchTitle) if match.index > 0 {
Text(matchTitle)
}
} }
Spacer() Spacer()
if let courtName { if let courtName {

@ -110,6 +110,24 @@ struct EventListView: View {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow") 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 { private func _federalTournamentView(_ federalTournament: FederalTournament) -> some View {

@ -12,7 +12,8 @@ struct ToolboxView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@State private var didResetApiCalls: Bool = false
var body: some View { var body: some View {
@Bindable var navigation = navigation @Bindable var navigation = navigation
NavigationStack(path: $navigation.toolboxPath) { NavigationStack(path: $navigation.toolboxPath) {
@ -20,6 +21,11 @@ struct ToolboxView: View {
Section { Section {
Text("Version de l'application").badge(PadelClubApp.appVersion) Text("Version de l'application").badge(PadelClubApp.appVersion)
.onTapGesture(count: 5) {
StoreCenter.main.resetApiCalls()
didResetApiCalls = true
}
SupportButtonView(contentIsUnavailable: false) SupportButtonView(contentIsUnavailable: false)
if StoreCenter.main.userId == "94f45ed2-8938-4c32-a4b6-e4525073dd33" { 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) 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) .navigationTitle(TabDestination.toolbox.title)
} }
} }

@ -56,13 +56,15 @@ struct SchedulerView: View {
} }
} footer: { } footer: {
if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight { if tournament.isAnimation() == false {
Button { if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight {
tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat() Button {
} label: { tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat()
Text("devrait être joué au moins en " + tournament.groupStageSmartMatchFormat().format + ", ") + Text("modifier ?").underline().foregroundStyle(.master) } label: {
Text("devrait être joué au moins en " + tournament.groupStageSmartMatchFormat().format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
}
.buttonStyle(.plain)
} }
.buttonStyle(.plain)
} }
} }
@ -123,19 +125,21 @@ struct SchedulerView: View {
} header: { } header: {
Text(round.titleLabel()) Text(round.titleLabel())
} footer: { } footer: {
let federalFormat = tournament.roundSmartMatchFormat(round.index) if tournament.isAnimation() == false {
if round.matchFormat.weight > federalFormat.weight { let federalFormat = tournament.roundSmartMatchFormat(round.index)
Button { if round.matchFormat.weight > federalFormat.weight {
round.updateMatchFormatAndAllMatches(federalFormat) Button {
do { round.updateMatchFormatAndAllMatches(federalFormat)
try self.tournamentStore.rounds.addOrUpdate(instance: round) do {
} catch { try self.tournamentStore.rounds.addOrUpdate(instance: round)
Logger.error(error) } catch {
Logger.error(error)
}
} label: {
Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
} }
} label: { .buttonStyle(.plain)
Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
} }
.buttonStyle(.plain)
} }
} }
@ -166,7 +170,7 @@ struct SchedulerView: View {
} header: { } header: {
Text("Match de classement \(round.roundTitle(.short))") Text("Match de classement \(round.roundTitle(.short))")
} footer: { } 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) let federalFormat = tournament.loserBracketSmartMatchFormat(1)
if semi.matchFormat.weight > federalFormat.weight { if semi.matchFormat.weight > federalFormat.weight {
Button { Button {

@ -102,8 +102,8 @@ struct EditablePlayerView: View {
} }
if let mail = player.email, let url = URL(string: "mailto:\(mail)") { if let mail = player.email, let url = URL(string: "mailto:\(mail)") {
Link(destination: url) { Link(destination: url) {
Label("SMS", systemImage: "message") Label("Mail", systemImage: "envelope")
Text(mail) Text(mail).lineLimit(1)
} }
} }

@ -10,41 +10,58 @@ import SwiftUI
struct LoserRoundView: View { struct LoserRoundView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed
let loserBracket: LoserRound
let loserRounds: [Round]
@State private var isEditingTournamentSeed: Bool = false
private func _roundDisabled() -> Bool { 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 { var body: some View {
List { List {
if isEditingTournamentSeed == true { if isEditingTournamentSeed.wrappedValue == true {
_editingView() _editingView()
} }
ForEach(loserRounds) { loserRound in ForEach(loserBracket.rounds) { loserRound in
if true { let matches = _matches(loserRoundId: loserRound.id)
if matches.isEmpty == false {
Section { Section {
let matches = loserRound.playedMatches().sorted(by: \.index)
ForEach(matches) { match in ForEach(matches) { match in
MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle) MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle)
.overlay { .overlay {
if match.disabled /*&& isEditingTournamentSeed*/ { if match.disabled && isEditingTournamentSeed.wrappedValue == true {
Image(systemName: "xmark") Image(systemName: "xmark")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.opacity(0.8) .opacity(0.6)
} }
} }
.disabled(match.disabled) .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: { } header: {
HStack { HStack {
Text(loserRound.roundTitle(.wide)) if let seedInterval = loserRound.seedInterval() {
let tournamentTeamCount = tournament.teamCount Text(seedInterval.localizedLabel(.wide))
if let seedIntervalPointRange = loserRound.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { let seedIntervalPointRange = seedInterval.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournament.teamCount)
Spacer() Spacer()
Text(seedIntervalPointRange) Text(seedIntervalPointRange)
.font(.caption) .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) .headerProminence(.increased)
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button(isEditingTournamentSeed == true ? "Valider" : "Modifier") { Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") {
isEditingTournamentSeed.toggle() isEditingTournamentSeed.wrappedValue.toggle()
} }
} }
} }
@ -67,13 +95,13 @@ struct LoserRoundView: View {
private func _editingView() -> some View { private func _editingView() -> some View {
if _roundDisabled() { if _roundDisabled() {
RowButtonView("Jouer ce tour", role: .destructive) { RowButtonView("Jouer ce tour", role: .destructive) {
loserRounds.forEach { round in loserBracket.rounds.forEach { round in
round.enableRound() round.enableRound()
} }
} }
} else { } else {
RowButtonView("Ne pas jouer ce tour", role: .destructive) { RowButtonView("Ne pas jouer ce tour", role: .destructive) {
loserRounds.forEach { round in loserBracket.rounds.forEach { round in
round.disableRound() round.disableRound()
} }
} }

@ -7,27 +7,118 @@
import SwiftUI 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 { struct LoserRound: Identifiable, Selectable {
let turnIndex: Int let turnIndex: Int
let rounds: [Round] 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 { var id: Int {
return turnIndex 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] { 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]() var rounds = [LoserRound]()
let allLoserRounds = upperBracketRound.loserRoundsAndChildren()
for (index, round) in loserRounds.enumerated() { 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 return rounds
} }
static func enabledLoserRounds(inLoserRounds loserRounds: [Round], inUpperBracketRound upperBracketRound: Round) -> [Round] { static func enabledLoserRounds(inLoserRounds loserRounds: [Round], inUpperBracketRound upperBracketRound: Round) -> [Round] {
let allLoserRounds = upperBracketRound.loserRoundsAndChildren()
return loserRounds.filter { loserRound in 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)" return "Tour #\(turnIndex + 1)"
} }
func badgeValue() -> Int? { func badgeValue() -> Int? {
let playedMatches: [Match] = self.rounds.flatMap { $0.playedMatches() } let runningMatches: [Match] = allMatches.filter { $0.disabled == false && $0.isRunning() }
let runningMatches: [Match] = playedMatches.filter { $0.isRunning() }
return runningMatches.count return runningMatches.count
} }
@ -55,32 +148,45 @@ extension LoserRound: Equatable {
} }
func badgeImage() -> Badge? { 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 { struct LoserRoundsView: View {
var upperBracketRound: Round var upperBracketRound: UpperRound
@State private var selectedRound: LoserRound? @State private var selectedRound: LoserRound?
let loserRounds: [Round] @State private var isEditingTournamentSeed = false
@State private var allDestinations: [LoserRound]
init(upperBracketRound: UpperRound) {
init(upperBracketRound: Round) {
self.upperBracketRound = upperBracketRound self.upperBracketRound = upperBracketRound
let _loserRounds = upperBracketRound.loserRounds() _selectedRound = State(wrappedValue: upperBracketRound.loserRounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? upperBracketRound.loserRounds.first(where: { $0.shouldBeDisplayed }))
self.loserRounds = _loserRounds }
let rounds = LoserRound.updateDestinations(fromLoserRounds: _loserRounds, inUpperBracketRound: upperBracketRound)
_allDestinations = State(wrappedValue: rounds) var destinations: [LoserRound] {
isEditingTournamentSeed ? upperBracketRound.loserRounds : upperBracketRound.loserRounds.filter({ $0.shouldBeDisplayed })
_selectedRound = State(wrappedValue: rounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? rounds.first)
} }
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: allDestinations, nilDestinationIsValid: false) GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: false)
LoserRoundView(loserRounds: selectedRound!.rounds) 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) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
} }

@ -22,6 +22,45 @@ struct RoundSettingsView: View {
List { List {
if tournament.shouldVerifyBracket { if tournament.shouldVerifyBracket {
Section { 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) { RowButtonView("Valider l'état du tableau", role: .destructive) {
tournament.shouldVerifyBracket = false tournament.shouldVerifyBracket = false
do { do {

@ -22,20 +22,36 @@ struct RoundView: View {
@State private var availableSeedGroup: SeedInterval? @State private var availableSeedGroup: SeedInterval?
@State private var showPrintScreen: Bool = false @State private var showPrintScreen: Bool = false
var round: Round var upperRound: UpperRound
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
private func _getAvailableSeedGroup() async { 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 { 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.spaceLeft.removeAll()
self.seedSpaceLeft.removeAll() self.seedSpaceLeft.removeAll()
let displayableMatches: [Match] = self.round.displayableMatches() let displayableMatches: [Match] = self.upperRound.round.displayableMatches()
displayableMatches.forEach { match in displayableMatches.forEach { match in
let count: Int = match.teamScores.count let count: Int = match.teamScores.count
if count == 0 { if count == 0 {
@ -57,8 +73,7 @@ struct RoundView: View {
var body: some View { var body: some View {
List { List {
let displayableMatches = round.displayableMatches().sorted(by: \.index) let displayableMatches = upperRound.round.displayableMatches().sorted(by: \.index)
let loserRounds = round.loserRounds()
if displayableMatches.isEmpty { if displayableMatches.isEmpty {
Section { Section {
ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball") ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball")
@ -71,30 +86,38 @@ struct RoundView: View {
} }
.tipStyle(tint: .master, asSection: true) .tipStyle(tint: .master, asSection: true)
if loserRounds.isEmpty == false { if upperRound.loserRounds.isEmpty == false {
let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle()
Section { Section {
NavigationLink { NavigationLink {
LoserRoundsView(upperBracketRound: round) LoserRoundsView(upperBracketRound: upperRound)
.environment(tournament) .environment(tournament)
.navigationTitle(correspondingLoserRoundTitle) .navigationTitle(upperRound.correspondingLoserRoundTitle)
} label: { } 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 { } else {
let disabledMatchesCount = BracketEditTip.matchesHidden let disabledMatchesCount = BracketEditTip.matchesHidden
if disabledMatchesCount > 0 { 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) TipView(bracketTip).tipStyle(tint: .green, asSection: true)
Section { Section {
let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: round.index) - disabledMatchesCount) let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: upperRound.round.index) - disabledMatchesCount)
LabeledContent { LabeledContent {
Text(leftToPlay.formatted()).font(.largeTitle) Text(leftToPlay.formatted()).font(.largeTitle)
} label: { } 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") Text("\(disabledMatchesCount) match\(disabledMatchesCount.pluralSuffix) désactivé\(disabledMatchesCount.pluralSuffix) automatiquement")
} }
} }
@ -106,7 +129,7 @@ struct RoundView: View {
if availableSeeds.isEmpty == false, let availableSeedGroup { if availableSeeds.isEmpty == false, let availableSeedGroup {
Section { Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { 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() await _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false self.isEditingTournamentSeed.wrappedValue = false
@ -230,16 +253,17 @@ struct RoundView: View {
} }
ForEach(displayableMatches) { match in ForEach(displayableMatches) { match in
let matchTitle = match.matchTitle(.short, inMatches: displayableMatches)
Section { Section {
MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle) MatchRowView(match: match, tournament: self.tournament, matchViewStyle: .sectionedStandardStyle, title: matchTitle)
} header: { } header: {
HStack { HStack {
Text(round.roundTitle(.wide)) Text(upperRound.round.roundTitle(.wide))
if round.index > 0 { if upperRound.round.index > 0 {
Text(match.matchTitle(.short, inMatches: displayableMatches)) Text(matchTitle)
} else { } else {
let tournamentTeamCount = tournament.teamCount 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() Spacer()
Text(seedIntervalPointRange) Text(seedIntervalPointRange)
.font(.caption) .font(.caption)
@ -262,15 +286,15 @@ struct RoundView: View {
Task { Task {
await _prepareRound() await _prepareRound()
} }
let seeds = round.seeds() let seeds = upperRound.round.seeds()
SlideToDeleteSeedTip.seeds = seeds.count SlideToDeleteSeedTip.seeds = seeds.count
PrintTip.seeds = seeds.count PrintTip.seeds = seeds.count
BracketEditTip.matchesHidden = round.getDisabledMatches().count BracketEditTip.matchesHidden = upperRound.round.getDisabledMatches().count
} }
.fullScreenCover(isPresented: showVisualDrawView) { .fullScreenCover(isPresented: showVisualDrawView) {
if let availableSeedGroup = selectedSeedGroup { if let availableSeedGroup = selectedSeedGroup {
let seeds = tournament.seeds(inSeedGroup: availableSeedGroup) let seeds = tournament.seeds(inSeedGroup: availableSeedGroup)
let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: round.index) let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: upperRound.round.index)
NavigationStack { NavigationStack {
SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in
Task { Task {

@ -9,16 +9,20 @@ import SwiftUI
struct RoundsView: View { struct RoundsView: View {
var tournament: Tournament var tournament: Tournament
@State private var selectedRound: Round? @State private var selectedRound: UpperRound?
@State private var isEditingTournamentSeed = false @State private var isEditingTournamentSeed = false
let destinations: [UpperRound]
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
let _destinations = tournament.rounds().map { UpperRound(round: $0) }
self.destinations = _destinations
let availableSeeds = tournament.availableSeeds() let availableSeeds = tournament.availableSeeds()
if tournament.shouldVerifyBracket && availableSeeds.isEmpty { if tournament.shouldVerifyBracket {
_selectedRound = State(wrappedValue: nil) _selectedRound = State(wrappedValue: nil)
} else { } 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 { if availableSeeds.isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false {
_isEditingTournamentSeed = State(wrappedValue: true) _isEditingTournamentSeed = State(wrappedValue: true)
@ -27,14 +31,14 @@ struct RoundsView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: tournament.rounds(), nilDestinationIsValid: true) GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: destinations, nilDestinationIsValid: true)
switch selectedRound { switch selectedRound {
case .none: case .none:
RoundSettingsView() RoundSettingsView()
.navigationTitle("Réglages") .navigationTitle("Réglages")
case .some(let selectedRound): case .some(let selectedRound):
RoundView(round: selectedRound).id(selectedRound.id) RoundView(upperRound: selectedRound).id(selectedRound.id)
.navigationTitle(selectedRound.roundTitle()) .navigationTitle(selectedRound.round.roundTitle())
} }
} }
.environment(\.isEditingTournamentSeed, $isEditingTournamentSeed) .environment(\.isEditingTournamentSeed, $isEditingTournamentSeed)

@ -16,6 +16,8 @@ enum FileImportCustomField: Int, Identifiable, CaseIterable {
case teamName case teamName
case lastName case lastName
case firstName case firstName
case phoneNumber
case email
case rank case rank
case licenceId case licenceId
case clubName case clubName
@ -44,6 +46,10 @@ enum FileImportCustomField: Int, Identifiable, CaseIterable {
return "Nom" return "Nom"
case .firstName: case .firstName:
return "Prénom" return "Prénom"
case .phoneNumber:
return "Téléphone"
case .email:
return "E-mail"
case .rank: case .rank:
return "Rang" return "Rang"
case .licenceId: 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 { Section {
RowButtonView("Importer pour tous les tournois") { RowButtonView("Importer pour tous les tournois") {
multiImport = true multiImport = true
@ -443,10 +449,20 @@ struct FileImportView: View {
await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: rankSourceDate) await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: rankSourceDate)
} }
let tournaments = tournament.eventObject()?.tournaments ?? [tournament] let event: Event? = tournament.eventObject()
for tournament in tournaments { if let event, event.tenupId != nil {
let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: tournament, fileProvider: fileProvider) var categoriesDone: [TournamentCategory] = []
self.teams += _teams 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 { await MainActor.run {

@ -23,7 +23,7 @@ struct BroadcastView: View {
let filter = CIFilter.qrCodeGenerator() let filter = CIFilter.qrCodeGenerator()
@State private var urlToShow: String? @State private var urlToShow: String?
@State private var tvMode: Bool = false @State private var tvMode: Bool = false
@State private var pageLink: PageLink = .teams @State private var pageLink: PageLink = .matches
let createAccountTip = CreateAccountTip() let createAccountTip = CreateAccountTip()
let tournamentPublishingTip = TournamentPublishingTip() let tournamentPublishingTip = TournamentPublishingTip()
@ -34,264 +34,245 @@ struct BroadcastView: View {
List { List {
if StoreCenter.main.userId == nil { if StoreCenter.main.userId == nil {
Section { Section {
TipView(createAccountTip) { action in ContentUnavailableView {
switch action.id { Label("Créer votre compte Padel Club", systemImage: "person.bust")
case CreateAccountTip.ActionKey.accessPadelClubWebPage.rawValue:
UIApplication.shared.open(URLs.main.url) } description: {
case CreateAccountTip.ActionKey.createAccount.rawValue: 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 navigation.selectedTab = .umpire
default: }
break
//todo RowButtonView("Jeter un oeil au site Padel Club") {
// case CreateAccountTip.ActionKey.learnMore.rawValue: UIApplication.shared.open(URLs.main.url)
// UIApplication.shared.open(URLs.padelClubLandingPage.url)
} }
} }
.tipStyle(tint: .master)
}
}
Section {
TipView(tournamentPublishingTip) { action in
UIApplication.shared.open(URLs.main.url)
} }
.tipStyle(tint: nil) } else {
} Section {
Section { TipView(tournamentPublishingTip) { action in
TipView(tournamentTVBroadcastTip) UIApplication.shared.open(URLs.main.url)
.tipStyle(tint: nil)
}
if tournament.isPrivate == false {
if let url = tournament.shareURL(.clubBroadcast) {
Section {
Link(destination: url) {
Text(url.absoluteString)
}
.contextMenu {
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = url.absoluteString
}
}
} header: {
Text("Lien pour la diffusion TV")
} footer: {
Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !")
} }
.tipStyle(tint: nil)
} }
Section { Section {
LabeledContent { let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast]
if tournament.isTournamentPublished() { Picker(selection: $pageLink) {
Image(systemName:"checkmark").foregroundStyle(.green) ForEach(links) { pageLink in
} else { Text(pageLink.localizedLabel()).tag(pageLink)
Text(tournament.publishedTournamentDate().formatted())
} }
} label: { } label: {
if tournament.isTournamentPublished() { Text("Choisir la page à partager")
Text("Publiée")
} else {
Text("Publication prévue")
}
}
if tournament.canBePublished() == false {
Text("Pour être visible automatiquement, le tournoi doit avoir été créé il y a 24h, avoir une structure et au moins 4 inscriptions.")
} }
.pickerStyle(.menu)
actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink))
} header: { } header: {
Text("Information sur le tournoi") Text("Lien du tournoi à partager")
} footer: {
if Date() < tournament.publishedTournamentDate() || tournament.canBePublished() == false {
HStack {
Spacer()
FooterButtonView(tournament.publishTournament ? "masquer sur le site" : "publier maintenant") {
tournament.publishTournament.toggle()
_save()
}
}
}
} }
Section { Section {
LabeledContent { let club = tournament.club()
if tournament.areTeamsPublished() { actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL())
Image(systemName:"checkmark").foregroundStyle(.green) actionForURL(title: "Padel Club", url: URLs.main.url)
} else { } header: {
Text(tournament.publishedTeamsDate().formatted()) Text("Autres liens")
} }
} label: {
if tournament.areTeamsPublished() { if tournament.isPrivate == false {
Text("Publiée") Section {
} else { TipView(tournamentTVBroadcastTip)
Text("Publication prévue") .tipStyle(tint: nil)
}
} }
Toggle(isOn: $tournament.hideTeamsWeight) { if let url = tournament.shareURL(.clubBroadcast) {
Text("Masquer les poids des équipes") Section {
} Link(destination: url) {
} header: { Text(url.absoluteString)
Text("Liste des équipes")
} footer: {
if Date() < tournament.publishedTeamsDate() {
HStack {
Spacer()
FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") {
tournament.publishTeams.toggle()
_save()
} }
.contextMenu {
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = url.absoluteString
}
}
} header: {
Text("Lien pour la diffusion TV")
} footer: {
Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !")
} }
} }
}
Section { Section {
LabeledContent { LabeledContent {
if tournament.areSummonsPublished() { if tournament.isTournamentPublished() {
Image(systemName:"checkmark").foregroundStyle(.green) Image(systemName:"checkmark").foregroundStyle(.green)
} else { } else {
Text(tournament.publishedTeamsDate().formatted()) Text(tournament.publishedTournamentDate().formatted())
}
} label: {
if tournament.isTournamentPublished() {
Text("Publiée")
} else {
Text("Publication prévue")
}
} }
} label: {
if tournament.areSummonsPublished() { if tournament.canBePublished() == false {
Text("Publiées") Text("Pour être visible automatiquement, le tournoi doit avoir été créé il y a 24h, avoir une structure et au moins 4 inscriptions.")
} else {
Text("Publication prévue")
} }
} } header: {
} header: { Text("Information sur le tournoi")
Text("Convocations") } footer: {
} footer: { if Date() < tournament.publishedTournamentDate() || tournament.canBePublished() == false {
if Date() < tournament.publishedTeamsDate() { HStack {
HStack { Spacer()
Spacer() FooterButtonView(tournament.publishTournament ? "masquer sur le site" : "publier maintenant") {
FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") { tournament.publishTournament.toggle()
tournament.publishSummons.toggle() _save()
_save() }
} }
} }
} }
}
if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() {
Section { Section {
let areGroupStagesPublished = tournament.areGroupStagesPublished()
LabeledContent { LabeledContent {
if areGroupStagesPublished { if tournament.areTeamsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green) Image(systemName:"checkmark").foregroundStyle(.green)
} else { } else {
Text(publishedGroupStagesDate.formatted()) Text(tournament.publishedTeamsDate().formatted())
} }
} label: { } label: {
if areGroupStagesPublished { if tournament.areTeamsPublished() {
Text("Publiées") Text("Publiée")
} else { } else {
Text("Publication prévue") Text("Publication prévue")
} }
} }
Toggle(isOn: $tournament.hideTeamsWeight) {
Text("Masquer les poids des équipes")
}
} header: { } header: {
Text("Poules") Text("Liste des équipes")
} footer: { } footer: {
if Date() < publishedGroupStagesDate { if Date() < tournament.publishedTeamsDate() {
HStack { HStack {
Spacer() Spacer()
FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") { FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") {
tournament.publishGroupStages.toggle() tournament.publishTeams.toggle()
_save() _save()
} }
} }
} }
} }
}
if let publishedBracketsDate = tournament.publishedBracketsDate() {
Section { Section {
let areBracketsPublished = tournament.areBracketsPublished()
LabeledContent { LabeledContent {
if areBracketsPublished { if tournament.areSummonsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green) Image(systemName:"checkmark").foregroundStyle(.green)
} else { } else {
Text(publishedBracketsDate.formatted()) Text(tournament.publishedTeamsDate().formatted())
} }
} label: { } label: {
if areBracketsPublished { if tournament.areSummonsPublished() {
Text("Publié") Text("Publiées")
} else { } else {
Text("Publication prévue") Text("Publication prévue")
} }
} }
} header: { } header: {
Text("Tableau") Text("Convocations")
} footer: { } footer: {
if Date() < publishedBracketsDate { if Date() < tournament.publishedTeamsDate() {
HStack { HStack {
Spacer() Spacer()
FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") { FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") {
tournament.publishBrackets.toggle() tournament.publishSummons.toggle()
_save() _save()
} }
} }
} }
} }
}
} if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() {
Section {
//todo waitinglist & info let areGroupStagesPublished = tournament.areGroupStagesPublished()
LabeledContent {
Section { if areGroupStagesPublished {
Toggle(isOn: $tournament.isPrivate) { Image(systemName:"checkmark").foregroundStyle(.green)
Text("Tournoi privé") } else {
} Text(publishedGroupStagesDate.formatted())
} footer: { }
let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))" } label: {
Text(.init(footerString)) if areGroupStagesPublished {
} Text("Publiées")
} else {
Section { Text("Publication prévue")
LabeledContent { }
actionForURL(URLs.main.url) }
} label: { } header: {
Text("Padel Club") Text("Poules")
} } footer: {
if Date() < publishedGroupStagesDate {
let club = tournament.club() HStack {
LabeledContent { Spacer()
if let clubURL = club?.shareURL() { FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") {
actionForURL(clubURL) tournament.publishGroupStages.toggle()
} _save()
} label: { }
Text("Club") }
if let club { }
Text(club.clubTitle()) }
} else {
Text("Aucun club indiqué pour ce tournoi")
} }
}
if let publishedBracketsDate = tournament.publishedBracketsDate() {
if let url = tournament.shareURL(pageLink) { Section {
LabeledContent { let areBracketsPublished = tournament.areBracketsPublished()
actionForURL(url) LabeledContent {
} label: { if areBracketsPublished {
Text("Tournoi") Image(systemName:"checkmark").foregroundStyle(.green)
Text(pageLink.localizedLabel()) } else {
Text(publishedBracketsDate.formatted())
}
} label: {
if areBracketsPublished {
Text("Publié")
} else {
Text("Publication prévue")
}
}
} header: {
Text("Tableau")
} footer: {
if Date() < publishedBracketsDate {
HStack {
Spacer()
FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") {
tournament.publishBrackets.toggle()
_save()
}
}
}
}
} }
} }
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] //todo waitinglist & info
Picker(selection: $pageLink) {
ForEach(links) { pageLink in Section {
Text(pageLink.localizedLabel()).tag(pageLink) Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privé")
} }
} label: { } footer: {
Text("Modifier la page du tournoi à partager") let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))"
Text(.init(footerString))
} }
.pickerStyle(.menu)
} header: {
Text("Liens à partager")
.textCase(nil)
} }
} }
.headerProminence(.increased) .headerProminence(.increased)
.navigationTitle("Publication") .navigationTitle("Publication")
@ -343,31 +324,48 @@ struct BroadcastView: View {
} }
@ViewBuilder @ViewBuilder
func actionForURL(_ url: URL, removeSource: Bool = false) -> some View { func actionForURL(title: String, description: String? = nil, url: URL?, removeSource: Bool = false) -> some View {
Menu { if let url {
Button { Menu {
UIApplication.shared.open(url) Button {
UIApplication.shared.open(url)
} label: {
Label("Voir", systemImage: "safari")
}
Button {
urlToShow = url.absoluteString
} label: {
Label("QRCode", systemImage: "qrcode")
}
ShareLink(item: url) {
Label("Partager le lien", systemImage: "link")
}
} label: { } label: {
Label("Voir", systemImage: "safari") LabeledContent {
Image(systemName: "square.and.arrow.up")
.foregroundColor(.master)
} label: {
Text(title)
.foregroundColor(.primary)
if let description {
Text(description)
.foregroundColor(.secondary)
}
}
} }
.buttonStyle(.plain)
Button { } else {
urlToShow = url.absoluteString LabeledContent {
Image(systemName: "xmark").foregroundColor(.logoYellow)
} label: { } label: {
Label("QRCode", systemImage: "qrcode") Text(title)
} if let description {
Text(description)
ShareLink(item: url) { }
Label("Partager le lien", systemImage: "link")
}
} label: {
HStack {
Spacer()
Image(systemName: "square.and.arrow.up")
} }
} }
.frame(maxWidth: .infinity)
.buttonStyle(.borderless)
} }

@ -43,13 +43,13 @@ struct InscriptionInfoView: View {
} }
.listRowView(color: .cyan) .listRowView(color: .cyan)
LabeledContent { // LabeledContent {
Text(selectedTeams.filter { $0.confirmed() }.count.formatted()) // Text(selectedTeams.filter { $0.confirmed() }.count.formatted())
} label: { // } label: {
Text("Paires ayant confirmées") // Text("Paires ayant confirmées")
Text("Vous avez noté la confirmation de l'équipe") // Text("Vous avez noté la confirmation de l'équipe")
} // }
.listRowView(color: .green) // .listRowView(color: .green)
} }
Section { Section {

@ -46,7 +46,12 @@ struct TournamentStatusView: View {
RowButtonView("Supprimer le tournoi", role: .destructive) { RowButtonView("Supprimer le tournoi", role: .destructive) {
if tournament.payment == nil { if tournament.payment == nil {
do { do {
let event = tournament.eventObject()
let isLastTournament = event?.tournaments.count == 1
try dataStore.tournaments.delete(instance: tournament) try dataStore.tournaments.delete(instance: tournament)
if let event, isLastTournament {
try dataStore.events.delete(instance: event)
}
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -62,10 +67,6 @@ struct TournamentStatusView: View {
} }
navigation.path = NavigationPath() navigation.path = NavigationPath()
} }
} footer: {
if tournament.payment == nil {
Text("")
}
} }
if tournament.hasEnded() == false && tournament.isCanceled == false { if tournament.hasEnded() == false && tournament.isCanceled == false {
@ -81,7 +82,7 @@ struct TournamentStatusView: View {
dismiss() dismiss()
} }
} footer: { } 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 showSubscriptionView: Bool = false
@State private var registrationIssues: Int? = nil @State private var registrationIssues: Int? = nil
@State private var sortedTeams: [TeamRegistration] = [] @State private var sortedTeams: [TeamRegistration] = []
@State private var unfilteredTeams: [TeamRegistration] = []
@State private var walkoutTeams: [TeamRegistration] = [] @State private var walkoutTeams: [TeamRegistration] = []
@State private var unsortedTeamsWithoutWO: [TeamRegistration] = [] @State private var unsortedTeamsWithoutWO: [TeamRegistration] = []
@State private var unsortedPlayers: [PlayerRegistration] = [] @State private var unsortedPlayers: [PlayerRegistration] = []
@ -124,7 +123,6 @@ struct InscriptionManagerView: View {
private func _clearScreen() { private func _clearScreen() {
teamPaste = nil teamPaste = nil
unsortedPlayers.removeAll() unsortedPlayers.removeAll()
unfilteredTeams.removeAll()
walkoutTeams.removeAll() walkoutTeams.removeAll()
unsortedTeamsWithoutWO.removeAll() unsortedTeamsWithoutWO.removeAll()
sortedTeams.removeAll() sortedTeams.removeAll()
@ -143,7 +141,7 @@ struct InscriptionManagerView: View {
} }
private func _setHash() async { private func _setHash() async {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -187,7 +185,7 @@ struct InscriptionManagerView: View {
_managementView() _managementView()
if _isEditingTeam() { if _isEditingTeam() {
_buildingTeamView() _buildingTeamView()
} else if unfilteredTeams.isEmpty { } else if sortedTeams.isEmpty {
_inscriptionTipsView() _inscriptionTipsView()
} else { } else {
_teamRegisteredView() _teamRegisteredView()
@ -332,6 +330,15 @@ struct InscriptionManagerView: View {
UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament) UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament)
.tint(.master) .tint(.master)
} }
.onChange(of: filterMode) {
_prepareTeams()
}
.onChange(of: sortingMode) {
_prepareTeams()
}
.onChange(of: byDecreasingOrdering) {
_prepareTeams()
}
.toolbar { .toolbar {
if _isEditingTeam() { if _isEditingTeam() {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
@ -437,7 +444,7 @@ struct InscriptionManagerView: View {
} }
private func _prepareStats() async { private func _prepareStats() async {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -452,7 +459,7 @@ struct InscriptionManagerView: View {
} }
private func _prepareTeams() { private func _prepareTeams() {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -460,6 +467,9 @@ struct InscriptionManagerView: View {
} }
#endif #endif
sortedTeams = tournament.sortedTeams() sortedTeams = tournament.sortedTeams()
}
var filteredTeams: [TeamRegistration] {
var teams = sortedTeams var teams = sortedTeams
if filterMode == .walkOut { if filterMode == .walkOut {
@ -471,14 +481,14 @@ struct InscriptionManagerView: View {
} }
if byDecreasingOrdering { if byDecreasingOrdering {
self.unfilteredTeams = teams.reversed() return teams.reversed()
} else { } else {
self.unfilteredTeams = teams return teams
} }
} }
private func _getIssues() async { private func _getIssues() async {
#if DEBUG_TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -502,10 +512,10 @@ struct InscriptionManagerView: View {
if presentSearch == false { if presentSearch == false {
_rankHandlerView() _rankHandlerView()
_relatedTips() _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 { if teams.isEmpty && searchField.isEmpty == false {
ContentUnavailableView { ContentUnavailableView {
@ -716,7 +726,7 @@ struct InscriptionManagerView: View {
} }
} }
private func _informationView(count: Int) -> some View { private func _informationView() -> some View {
Section { Section {
LabeledContent { LabeledContent {
Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
@ -878,7 +888,7 @@ struct InscriptionManagerView: View {
private func _isDuplicate() -> Bool { private func _isDuplicate() -> Bool {
let ids : [String?] = _currentSelectionIds() let ids : [String?] = _currentSelectionIds()
if unfilteredTeams.anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) { if sortedTeams.anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) {
return true return true
} }
return false return false

@ -26,7 +26,7 @@ enum CallDestination: Identifiable, Selectable, Equatable {
} }
} }
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
switch self { switch self {
case .seeds: case .seeds:
return "Têtes de série" 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 { switch self {
case .summary: case .summary:
return "Bilan" return "Bilan"
case .groupStage(let groupStage): case .groupStage(let groupStage):
return groupStage.selectionLabel() return groupStage.selectionLabel(index: index)
case .bracket(let round): case .bracket(let round):
return round.selectionLabel() return round.selectionLabel(index: index)
case .all: case .all:
return "Tous" return "Tous"
} }

@ -19,6 +19,9 @@ struct TournamentRankView: View {
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
@State private var runningMatches: [Match]?
@State private var matchesLeft: [Match]?
var isEditingTeam: Binding<Bool> { var isEditingTeam: Binding<Bool> {
Binding { Binding {
@ -30,11 +33,24 @@ struct TournamentRankView: View {
var body: some View { var body: some View {
List { List {
@Bindable var tournament = tournament @Bindable var tournament = tournament
let matchs = tournament.runningMatches(tournament.allMatches())
let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil }) let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil })
Section { Section {
LabeledContent { 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: { } label: {
Text("Matchs en cours") 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) { .alert("Position", isPresented: isEditingTeam) {
if let selectedTeam { if let selectedTeam {
@Bindable var team = selectedTeam @Bindable var team = selectedTeam

@ -31,7 +31,7 @@ enum ScheduleDestination: String, Identifiable, Selectable, Equatable {
case scheduleGroupStage case scheduleGroupStage
case scheduleBracket case scheduleBracket
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
switch self { switch self {
case .scheduleGroupStage: case .scheduleGroupStage:
return "Poules" return "Poules"
@ -92,7 +92,7 @@ struct TournamentScheduleView: View {
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .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) } var id: String { String(describing: self) }
func selectionLabel() -> String { func selectionLabel(index: Int) -> String {
switch self { switch self {
case .status: case .status:
return "Statut" return "Statut"

@ -9,7 +9,7 @@ import SwiftUI
struct TournamentBuildView: View { struct TournamentBuildView: View {
var tournament: Tournament 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 groupStageStatus: (String, TeamRegistration.TeamRange?)?
@State private var callStatus: Tournament.TournamentStatus? @State private var callStatus: Tournament.TournamentStatus?
@State private var scheduleStatus: Tournament.TournamentStatus? @State private var scheduleStatus: Tournament.TournamentStatus?
@ -64,8 +64,10 @@ struct TournamentBuildView: View {
} label: { } label: {
Text("Tableau") Text("Tableau")
if tournament.shouldVerifyBracket { if tournament.shouldVerifyBracket {
Text("Vérifier la tableau").foregroundStyle(.logoRed) Text("Vérifier le tableau").foregroundStyle(.logoRed)
} else if let range = bracketStatus?.1 { } else if let description = bracketStatus?.1 {
Text(description)
} else if let range = bracketStatus?.2 {
HStack { HStack {
if let left = range.left { if let left = range.left {
Text(left.weight.formatted()) Text(left.weight.formatted())
@ -98,50 +100,48 @@ struct TournamentBuildView: View {
TournamentBroadcastRowView(tournament: tournament) TournamentBroadcastRowView(tournament: tournament)
} }
if state != .finished { NavigationLink(value: Screen.schedule) {
NavigationLink(value: Screen.schedule) { let tournamentStatus = scheduleStatus
let tournamentStatus = scheduleStatus LabeledContent {
LabeledContent { if let tournamentStatus {
if let tournamentStatus { Text(tournamentStatus.completion)
Text(tournamentStatus.completion) } else {
} else { ProgressView()
ProgressView()
}
} label: {
Text("Horaires")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
} }
} } label: {
.task { Text("Horaires et formats")
scheduleStatus = await tournament.scheduleStatus() if let tournamentStatus {
} Text(tournamentStatus.label).lineLimit(1)
} else {
NavigationLink(value: Screen.call) { Text(" ")
let tournamentStatus = callStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: {
Text("Convocations")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
} }
} }
.task { }
callStatus = await tournament.callStatus() .task {
} scheduleStatus = await tournament.scheduleStatus()
} }
NavigationLink(value: Screen.call) {
let tournamentStatus = callStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: {
Text("Convocations")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
}
}
.task {
callStatus = await tournament.callStatus()
}
NavigationLink(value: Screen.cashier) { NavigationLink(value: Screen.cashier) {
let tournamentStatus = cashierStatus let tournamentStatus = cashierStatus
LabeledContent { LabeledContent {

@ -28,17 +28,23 @@ struct TournamentInitView: View {
NavigationLink(value: Screen.structure) { NavigationLink(value: Screen.structure) {
LabeledContent { LabeledContent {
Text(tournament.structureDescriptionLocalizedLabel()) if tournament.state() == .initial {
Text("à valider")
} else {
Image(systemName: "checkmark").foregroundStyle(.green)
}
} label: { } label: {
LabelStructure() LabelStructure()
Text(tournament.structureDescriptionLocalizedLabel())
} }
} }
NavigationLink(value: Screen.settings) { NavigationLink(value: Screen.settings) {
LabeledContent { LabeledContent {
Text(tournament.settingsDescriptionLocalizedLabel()) Text(tournament.localizedTournamentType())
} label: { } label: {
LabelSettings() LabelSettings()
Text("Formats, club, prix et plus")
} }
} }

@ -45,9 +45,6 @@ struct TournamentView: View {
var body: some View { var body: some View {
VStack(spacing: 0.0) { VStack(spacing: 0.0) {
List { List {
TipView(tournamentRunningTip)
.tipStyle(tint: nil)
if tournament.state() != .finished { if tournament.state() != .finished {
SubscriptionInfoView() SubscriptionInfoView()
} }
@ -194,6 +191,7 @@ struct TournamentView: View {
} }
} label: { } label: {
LabelOptions() LabelOptions()
.popoverTip(tournamentRunningTip)
} }
} }
} }

@ -85,7 +85,7 @@ struct LoginView: View {
ContentUnavailableView { ContentUnavailableView {
Label("Vérifiez vos emails.", systemImage: "envelope.badge") Label("Vérifiez vos emails.", systemImage: "envelope.badge")
} description: { } 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: { } actions: {
SupportButtonView(contentIsUnavailable: true) SupportButtonView(contentIsUnavailable: true)
} }

Loading…
Cancel
Save