multistore
Razmig Sarkissian 2 years ago
parent 55b69bc9b4
commit 992d7fb744
  1. 79
      PadelClub.xcodeproj/project.pbxproj
  2. 171
      PadelClub/Data/GroupStage.swift
  3. 140
      PadelClub/Data/Match.swift
  4. 3
      PadelClub/Data/PlayerRegistration.swift
  5. 12
      PadelClub/Data/TeamRegistration.swift
  6. 7
      PadelClub/Data/TeamScore.swift
  7. 12
      PadelClub/Data/Tournament.swift
  8. 17
      PadelClub/Extensions/Sequence+Extensions.swift
  9. 16
      PadelClub/Manager/Network/NetworkFederalService.swift
  10. 9
      PadelClub/Manager/Network/NetworkManagerError.swift
  11. 4
      PadelClub/Manager/PadelRule.swift
  12. 2
      PadelClub/Manager/SourceFileManager.swift
  13. 99
      PadelClub/ViewModel/MatchDescriptor.swift
  14. 33
      PadelClub/ViewModel/SetDescriptor.swift
  15. 6
      PadelClub/Views/Club/ClubSearchView.swift
  16. 4
      PadelClub/Views/Club/ClubsView.swift
  17. 4
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  18. 45
      PadelClub/Views/Components/MatchListView.swift
  19. 34
      PadelClub/Views/Components/RowButtonView.swift
  20. 4
      PadelClub/Views/Event/EventCreationView.swift
  21. 4
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  22. 107
      PadelClub/Views/GroupStage/GroupStageTeamView.swift
  23. 347
      PadelClub/Views/GroupStage/GroupStageView.swift
  24. 71
      PadelClub/Views/GroupStage/GroupStagesView.swift
  25. 2
      PadelClub/Views/Match/MatchDateView.swift
  26. 73
      PadelClub/Views/Match/MatchDetailView.swift
  27. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  28. 31
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  29. 4
      PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift
  30. 18
      PadelClub/Views/Navigation/MainView.swift
  31. 2
      PadelClub/Views/Navigation/PadelClubView.swift
  32. 2
      PadelClub/Views/Player/Components/PlayerPopoverView.swift
  33. 8
      PadelClub/Views/Round/RoundSettingsView.swift
  34. 104
      PadelClub/Views/Score/EditScoreView.swift
  35. 50
      PadelClub/Views/Score/PointSelectionView.swift
  36. 26
      PadelClub/Views/Score/PointView.swift
  37. 215
      PadelClub/Views/Score/SetInputView.swift
  38. 57
      PadelClub/Views/Score/SetLabelView.swift
  39. 23
      PadelClub/Views/Shared/MatchTypeSmallSelectionView.swift
  40. 2
      PadelClub/Views/Team/TeamPickerView.swift
  41. 2
      PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift
  42. 16
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  43. 2
      PadelClub/Views/Tournament/TournamentView.swift
  44. 26
      PadelClub/Views/ViewModifiers/TabItemModifier.swift

@ -102,6 +102,7 @@
FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */; }; FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */; };
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; }; FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; };
FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; }; FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; };
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */; };
FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; }; FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; };
FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; };
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; };
@ -170,12 +171,23 @@
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; };
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; };
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; };
FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; };
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; };
FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; };
FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; };
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FFCFBFFD2BBBE86600B82851 /* Algorithms */; };
FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; };
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; };
FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0112BBC3E1A00B82851 /* PointView.swift */; };
FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */; };
FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */; };
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */; };
FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; };
FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; };
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; };
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; }; FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; };
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784032B91C280000F62A6 /* EmptyActivityView.swift */; }; FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784032B91C280000F62A6 /* EmptyActivityView.swift */; };
@ -332,6 +344,7 @@
FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; }; FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectablePlayerListView.swift; sourceTree = "<group>"; }; FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectablePlayerListView.swift; sourceTree = "<group>"; };
FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedPlayerView.swift; sourceTree = "<group>"; }; FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedPlayerView.swift; sourceTree = "<group>"; };
FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItemModifier.swift; sourceTree = "<group>"; };
FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; }; FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; };
FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = "<group>"; }; FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = "<group>"; };
@ -401,12 +414,22 @@
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; }; FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; };
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; }; FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; };
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; };
FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = "<group>"; };
FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; };
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = "<group>"; }; FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = "<group>"; };
FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; }; FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; };
FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; }; FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; };
FFCFC0012BBC39A600B82851 /* EditScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScoreView.swift; sourceTree = "<group>"; };
FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointSelectionView.swift; sourceTree = "<group>"; };
FFCFC0112BBC3E1A00B82851 /* PointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointView.swift; sourceTree = "<group>"; };
FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDescriptor.swift; sourceTree = "<group>"; };
FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetInputView.swift; sourceTree = "<group>"; };
FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetLabelView.swift; sourceTree = "<group>"; };
FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTypeSmallSelectionView.swift; sourceTree = "<group>"; };
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = "<group>"; };
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; }; FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; };
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; }; FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
FFD784012B91C1B4000F62A6 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; }; FFD784012B91C1B4000F62A6 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
@ -426,6 +449,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */,
FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */, FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -562,6 +586,7 @@
FFC83D4B2BB807C200750834 /* Round */, FFC83D4B2BB807C200750834 /* Round */,
FF967CF92BAEE11500A9A3BD /* GroupStage */, FF967CF92BAEE11500A9A3BD /* GroupStage */,
FF967CFE2BAEEF5A00A9A3BD /* Match */, FF967CFE2BAEEF5A00A9A3BD /* Match */,
FFCFC00B2BBC39A600B82851 /* Score */,
FF967D072BAF3D3000A9A3BD /* Team */, FF967D072BAF3D3000A9A3BD /* Team */,
FF089EB92BB011EE00F0AEC7 /* Player */, FF089EB92BB011EE00F0AEC7 /* Player */,
FF3F74F72B919F96004CFE0E /* Tournament */, FF3F74F72B919F96004CFE0E /* Tournament */,
@ -613,6 +638,7 @@
C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */, C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */,
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */, FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */,
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */, FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */,
FFBF065D2BBD8040009D6715 /* MatchListView.swift */,
FF967CF72BAEDF0000A9A3BD /* Labels.swift */, FF967CF72BAEDF0000A9A3BD /* Labels.swift */,
); );
path = Components; path = Components;
@ -784,6 +810,8 @@
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */, FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */,
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */, FFB9C8702BBADDE200A0EF4F /* Selectable.swift */,
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */, FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */,
FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */,
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */,
); );
path = ViewModel; path = ViewModel;
sourceTree = "<group>"; sourceTree = "<group>";
@ -796,6 +824,7 @@
FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */, FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */,
FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */, FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */,
FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */, FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */,
FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -859,6 +888,7 @@
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */,
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */,
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */, FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */,
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */,
); );
path = GroupStage; path = GroupStage;
sourceTree = "<group>"; sourceTree = "<group>";
@ -896,6 +926,18 @@
path = Round; path = Round;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FFCFC00B2BBC39A600B82851 /* Score */ = {
isa = PBXGroup;
children = (
FFCFC0012BBC39A600B82851 /* EditScoreView.swift */,
FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */,
FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */,
FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */,
FFCFC0112BBC3E1A00B82851 /* PointView.swift */,
);
path = Score;
sourceTree = "<group>";
};
FFD783FB2B91B919000F62A6 /* Agenda */ = { FFD783FB2B91B919000F62A6 /* Agenda */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -913,6 +955,7 @@
children = ( children = (
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */, FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */,
FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */, FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */,
FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */,
); );
path = ViewModifiers; path = ViewModifiers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -967,6 +1010,9 @@
dependencies = ( dependencies = (
); );
name = PadelClub; name = PadelClub;
packageProductDependencies = (
FFCFBFFD2BBBE86600B82851 /* Algorithms */,
);
productName = PadelClub; productName = PadelClub;
productReference = C425D3FD2B6D249D002A7B48 /* PadelClub.app */; productReference = C425D3FD2B6D249D002A7B48 /* PadelClub.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -1039,6 +1085,9 @@
Base, Base,
); );
mainGroup = C425D3F42B6D249D002A7B48; mainGroup = C425D3F42B6D249D002A7B48;
packageReferences = (
FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
);
productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */; productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = ( projectReferences = (
@ -1160,6 +1209,7 @@
FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */,
FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */,
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */,
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */,
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */, FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */,
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */, FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */,
C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */, C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */,
@ -1182,7 +1232,9 @@
FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */,
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */,
FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */, FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */,
FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */,
FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */, FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */,
FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */, FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */,
FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */, FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */,
@ -1201,6 +1253,7 @@
FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */, FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */,
FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */, FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */,
C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */, C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */,
FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */,
FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */,
FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */, FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */,
C4A47D7D2B73CDC300ADC637 /* ClubV1.swift in Sources */, C4A47D7D2B73CDC300ADC637 /* ClubV1.swift in Sources */,
@ -1212,6 +1265,7 @@
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */, FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */,
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */, FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */,
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */, FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */, FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */, C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */,
@ -1230,15 +1284,19 @@
FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */, FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */,
FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */, FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */,
FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */, FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */,
FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */, C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */, FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */, FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */,
FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */,
FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */,
FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */,
C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */, C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */,
FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */, FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */,
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */,
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */,
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */, FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */,
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
@ -1263,11 +1321,13 @@
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */,
C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */,
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */,
FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */,
FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */,
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */, FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */,
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */, FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */, FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
@ -1611,6 +1671,25 @@
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-algorithms.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.2.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
FFCFBFFD2BBBE86600B82851 /* Algorithms */ = {
isa = XCSwiftPackageProductDependency;
package = FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
productName = Algorithms;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */
FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = { FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = {
isa = XCVersionGroup; isa = XCVersionGroup;

@ -7,6 +7,7 @@
import Foundation import Foundation
import LeStorage import LeStorage
import Algorithms
@Observable @Observable
class GroupStage: ModelObject, Storable { class GroupStage: ModelObject, Storable {
@ -36,8 +37,8 @@ class GroupStage: ModelObject, Storable {
self.startDate = startDate self.startDate = startDate
} }
func teamsAt(_ index: Int) -> TeamRegistration? { func teamAt(groupStagePosition: Int) -> TeamRegistration? {
teams().first(where: { $0.groupStagePosition == index }) teams().first(where: { $0.groupStagePosition == groupStagePosition })
} }
func tournamentObject() -> Tournament? { func tournamentObject() -> Tournament? {
@ -58,23 +59,25 @@ class GroupStage: ModelObject, Storable {
} }
func isRunning() -> Bool { // at least a match has started func isRunning() -> Bool { // at least a match has started
matches.anySatisfy({ $0.isRunning() }) _matches().anySatisfy({ $0.isRunning() })
} }
func hasStarted() -> Bool { // meaning at least one match is over func hasStarted() -> Bool { // meaning at least one match is over
matches.filter { $0.hasEnded() }.isEmpty == false _matches().filter { $0.hasEnded() }.isEmpty == false
} }
func hasEnded() -> Bool { func hasEnded() -> Bool {
if matches.isEmpty { return false } guard teams().count == size else { return false }
return matches.allSatisfy { $0.hasEnded() } let _matches = _matches()
if _matches.isEmpty { return false }
return _matches.allSatisfy { $0.hasEnded() }
} }
func buildMatches() { func buildMatches() {
removeMatches() _removeMatches()
var _matches = [Match]() var _matches = [Match]()
for i in 0..<numberOfMatchesToBuild { for i in 0..<_numberOfMatchesToBuild() {
let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat) let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat)
_matches.append(newMatch) _matches.append(newMatch)
} }
@ -82,24 +85,162 @@ class GroupStage: ModelObject, Storable {
try? DataStore.shared.matches.addOrUpdate(contentOfs: _matches) try? DataStore.shared.matches.addOrUpdate(contentOfs: _matches)
} }
func removeMatches() { func matches() -> [Match] {
let ordered = _matches()
if ordered.isEmpty == false && ordered.count == _matchOrder().count {
return _matchOrder().map {
ordered[$0]
}
} else {
return ordered
}
}
func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> String? {
if let scoreData = _score(forGroupStagePosition: groupStagePosition) {
return "\(scoreData.wins)/\(scoreData.loses) " + scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false)))
} else {
return nil
}
}
fileprivate func _score(forGroupStagePosition groupStagePosition: Int) -> TeamGroupStageScore? {
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
let wins = matches.filter { $0.winningTeamId == team.id }.count
let loses = matches.filter { $0.losingTeamId == team.id }.count
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition) }
let setDifference = differences.map { $0.set }.reduce(0,+)
let gameDifference = differences.map { $0.game }.reduce(0,+)
return (team, wins, loses, setDifference, gameDifference)
}
func matches(forGroupStagePosition groupStagePosition: Int) -> [Match] {
let combos = Array((0..<size).combinations(ofCount: 2))
var matchIndexes = [Int]()
for (index, combo) in combos.enumerated() {
if combo.contains(groupStagePosition) { //team is playing
matchIndexes.append(index)
}
}
return _matches().filter { matchIndexes.contains($0.index) }
}
func availableToStart() -> [Match] {
matches().filter({ $0.canBeStarted() && $0.isRunning() == false })
}
func runningMatches() -> [Match] {
matches().filter({ $0.isRunning() })
}
func readyMatches() -> [Match] {
matches().filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
}
func finishedMatches() -> [Match] {
matches().filter({ $0.hasEnded() })
}
private func _matchOrder() -> [Int] {
switch size {
case 3:
return [1, 2, 0]
case 4:
return [2, 3, 1, 4, 5, 0]
case 5:
return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9]
// return [3, 5, 8, 2, 6, 7, 1, 9, 4, 0]
case 6:
return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0]
//return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
default:
return []
}
}
private func _matchUp(for matchIndex: Int) -> [Int] {
Array((0..<size).combinations(ofCount: 2))[matchIndex]
}
func localizedMatchUpLabel(for matchIndex: Int) -> String {
let matchUp = _matchUp(for: matchIndex)
if let index = matchUp.first, let index2 = matchUp.last {
return "#\(index + 1) contre #\(index2 + 1)"
} else {
return "--"
}
}
func team(whichTeam team: TeamData, inMatchIndex matchIndex: Int) -> TeamRegistration? {
let _teams = _teams(for: matchIndex)
switch team {
case .one:
return _teams.first!
case .two:
return _teams.last!
}
}
private func _teams(for matchIndex: Int) -> [TeamRegistration?] {
let combinations = Array(0..<size).combinations(ofCount: 2).map {$0}
return combinations[safe: matchIndex]?.map { teamAt(groupStagePosition: $0) } ?? []
}
private func _removeMatches() {
try? deleteDependencies() try? deleteDependencies()
} }
var numberOfMatchesToBuild: Int { private func _numberOfMatchesToBuild() -> Int {
(size * (size - 1)) / 2 (size * (size - 1)) / 2
} }
var matches: [Match] { private func _matches() -> [Match] {
Store.main.filter { $0.groupStage == self.id } Store.main.filter { $0.groupStage == self.id }
} }
func teams() -> [TeamRegistration] { fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool
Store.main.filter { $0.groupStage == self.id } fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
fileprivate func _headToHead(_ whichTeam: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
let indexes = [whichTeam, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2))
if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) {
return whichTeam.id == match.losingTeamId
} else {
return false
}
}
func teams(_ sortedByScore: Bool = false) -> [TeamRegistration] {
let teams: [TeamRegistration] = Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil }
if sortedByScore {
return teams.compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in
let predicates: [TeamScoreAreInIncreasingOrder] = [
{ $0.wins < $1.wins },
{ $0.setDifference < $1.setDifference },
{ $0.gameDifference < $1.gameDifference},
{ self._headToHead($0.team, $1.team) },
{ $0.team.groupStagePosition! > $1.team.groupStagePosition! }
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}.map({ $0.team }).reversed()
} else {
return teams.sorted(by: \TeamRegistration.groupStagePosition!)
}
} }
override func deleteDependencies() throws { override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.matches) try Store.main.deleteDependencies(items: self._matches())
} }
} }
@ -120,6 +261,6 @@ extension GroupStage: Selectable {
} }
func badgeValue() -> Int? { func badgeValue() -> Int? {
nil runningMatches().count
} }
} }

@ -54,6 +54,10 @@ class Match: ModelObject, Storable {
} }
func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String { func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if let groupStageObject {
return groupStageObject.localizedMatchUpLabel(for: index)
}
switch displayStyle { switch displayStyle {
case .wide: case .wide:
return "Match \(indexInRound() + 1)" return "Match \(indexInRound() + 1)"
@ -124,6 +128,83 @@ class Match: ModelObject, Storable {
} }
} }
func setWalkOut(_ whichTeam: TeamData) {
let teamScoreWalkout = teamScore(whichTeam) ?? TeamScore(match: id, teamRegistration: team(whichTeam)?.id)
teamScoreWalkout.walkOut = 0
let teamScoreWinning = teamScore(whichTeam.otherTeam) ?? TeamScore(match: id, teamRegistration: team(whichTeam.otherTeam)?.id)
teamScoreWinning.walkOut = nil
try? DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning])
if endDate == nil {
endDate = Date()
}
winningTeamId = teamScoreWinning.teamRegistration
losingTeamId = teamScoreWalkout.teamRegistration
// matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex)
}
func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
updateScore(fromMatchDescriptor: matchDescriptor)
if endDate == nil {
endDate = Date()
}
// matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex)
winningTeamId = team(matchDescriptor.winner)?.id
losingTeamId = team(matchDescriptor.winner.otherTeam)?.id
}
func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, teamRegistration: team(.one)?.id)
teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",")
let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, teamRegistration: team(.two)?.id)
teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",")
try? DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo])
matchFormat = matchDescriptor.matchFormat
}
func validateMatch(fromStartDate: Date, toEndDate: Date, fieldSetup: MatchFieldSetup) {
if hasEnded() == false {
startDate = fromStartDate
// if match.isTournamentMatch() {
// switch fieldSetup {
// case .random:
// let field = match.freeFields.randomElement() ?? match.currentTournament?.freeFields.randomElement() ?? 1
// match.setupFieldAndStartDateIfPossible(field)
// case .field(let courtIndex):
// let fieldIndex = Int64(courtIndex)
// match.setupFieldAndStartDateIfPossible(fieldIndex)
// }
// }
} else {
startDate = fromStartDate
endDate = toEndDate
}
}
func canBeStarted() -> Bool {
let teams = teams()
guard teams.count == 2 else { return false }
guard hasEnded() == false else { return false }
guard hasStarted() == false else { return false }
return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0) == false })
}
func isTeamPlaying(_ team: TeamRegistration) -> Bool {
if isGroupStage() {
let isPlaying = groupStageObject?.runningMatches().filter({ $0.teams().contains(team) }).isEmpty == false
return isPlaying
} else {
//todo
return false
}
}
func isReady() -> Bool { func isReady() -> Bool {
teams().count == 2 teams().count == 2
} }
@ -133,7 +214,7 @@ class Match: ModelObject, Storable {
} }
func hasEnded() -> Bool { func hasEnded() -> Bool {
endDate != nil endDate != nil || hasWalkoutTeam() || winningTeamId != nil
} }
func isGroupStage() -> Bool { func isGroupStage() -> Bool {
@ -166,26 +247,33 @@ class Match: ModelObject, Storable {
func teams() -> [TeamRegistration] { func teams() -> [TeamRegistration] {
if groupStage != nil { if groupStage != nil {
return scores().compactMap({ $0.team }).sorted(by: \.groupStagePosition!) return [groupStageProjectedTeam(.one), groupStageProjectedTeam(.two)].compactMap { $0 }
} }
return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 } return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 }
} }
func groupStageProjectedTeam(_ team: TeamData) -> TeamRegistration? { func scoreDifference(_ whichTeam: Int) -> (set: Int, game: Int)? {
guard groupStage != nil else { return nil } guard let teamScoreTeam = teamScore(.one), let teamScoreOtherTeam = teamScore(.two) else { return nil }
var reverseValue = 1
switch team { if whichTeam == team(.two)?.groupStagePosition {
case .one: reverseValue = -1
if let teamId = topPreviousRoundMatch()?.winningTeamId {
return Store.main.findById(teamId)
} }
case .two: let endedSetsOne = teamScoreTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreTeam.isWalkOut())
if let teamId = bottomPreviousRoundMatch()?.winningTeamId { let endedSetsTwo = teamScoreOtherTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreOtherTeam.isWalkOut())
return Store.main.findById(teamId) var setDifference : Int = 0
if endedSetsOne.count == 1 {
setDifference = endedSetsOne[0] - endedSetsTwo[0]
} else {
setDifference = endedSetsOne.filter { $0 == matchFormat.setFormat.scoreToWin }.count - endedSetsTwo.filter { $0 == matchFormat.setFormat.scoreToWin }.count
} }
let zip = zip(endedSetsOne, endedSetsTwo)
let gameDifference = zip.map { ($0, $1) }.map { $0.0 - $0.1 }.reduce(0,+)
return (setDifference * reverseValue, gameDifference * reverseValue)
} }
return nil func groupStageProjectedTeam(_ team: TeamData) -> TeamRegistration? {
guard let groupStageObject else { return nil }
return groupStageObject.team(whichTeam: team, inMatchIndex: index)
} }
func seed(_ team: TeamData) -> TeamRegistration? { func seed(_ team: TeamData) -> TeamRegistration? {
@ -219,16 +307,17 @@ class Match: ModelObject, Storable {
} }
func teamWon(_ team: TeamData) -> Bool { func teamWon(_ team: TeamData) -> Bool {
true guard let winningTeamId else { return false }
return winningTeamId == self.team(team)?.id
} }
func team(_ team: TeamData) -> TeamRegistration? { func team(_ team: TeamData) -> TeamRegistration? {
if groupStage != nil { if groupStage != nil {
switch team { switch team {
case .one: case .one:
return teams().first return groupStageProjectedTeam(.one)
case .two: case .two:
return teams().last return groupStageProjectedTeam(.two)
} }
} else { } else {
switch team { switch team {
@ -245,7 +334,7 @@ class Match: ModelObject, Storable {
} }
func teamWalkOut(_ team: TeamData) -> Bool { func teamWalkOut(_ team: TeamData) -> Bool {
false teamScore(team)?.isWalkOut() == true
} }
func teamScore(_ team: TeamData) -> TeamScore? { func teamScore(_ team: TeamData) -> TeamScore? {
@ -313,3 +402,20 @@ class Match: ModelObject, Storable {
case _disabled = "disabled" case _disabled = "disabled"
} }
} }
enum MatchDateSetup: Hashable, Identifiable {
case inMinutes(Int)
case now
case customDate
var id: Int { hashValue }
}
enum MatchFieldSetup: Hashable, Identifiable {
case random
// case firstAvailable
case field(String)
var id: Int { hashValue }
}

@ -35,6 +35,8 @@ class PlayerRegistration: ModelObject, Storable {
var weight: Int = 0 var weight: Int = 0
var source: PlayerDataSource? var source: PlayerDataSource?
var hasArrived: Bool = false
internal init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, registrationType: Int? = nil, registrationDate: Date? = nil, sex: Int, source: PlayerDataSource? = nil) { internal init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, registrationType: Int? = nil, registrationDate: Date? = nil, sex: Int, source: PlayerDataSource? = nil) {
self.teamRegistration = teamRegistration self.teamRegistration = teamRegistration
self.firstName = firstName self.firstName = firstName
@ -251,6 +253,7 @@ class PlayerRegistration: ModelObject, Storable {
case _email = "email" case _email = "email"
case _weight = "weight" case _weight = "weight"
case _source = "source" case _source = "source"
case _hasArrived = "hasArrived"
} }

@ -18,7 +18,7 @@ class TeamRegistration: ModelObject, Storable {
var registrationDate: Date? var registrationDate: Date?
var callDate: Date? var callDate: Date?
var bracketPosition: Int? var bracketPosition: Int?
var groupStagePosition: Int? var groupStagePosition: Int? //todo devrait être non nil ?
var comment: String? var comment: String?
var source: String? var source: String?
var sourceValue: String? var sourceValue: String?
@ -32,6 +32,7 @@ class TeamRegistration: ModelObject, Storable {
var weight: Int = 0 var weight: Int = 0
var lockWeight: Int? var lockWeight: Int?
var confirmationDate: Date? var confirmationDate: Date?
var qualified: Bool = false
internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) { internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) {
self.tournament = tournament self.tournament = tournament
@ -159,7 +160,11 @@ class TeamRegistration: ModelObject, Storable {
} }
} }
func available() -> Bool { func canPlay() -> Bool {
teamScores().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived })
}
func availableForSeedPick() -> Bool {
groupStage == nil && bracketPosition == nil groupStage == nil && bracketPosition == nil
} }
@ -203,7 +208,7 @@ class TeamRegistration: ModelObject, Storable {
} }
} }
func qualified() -> Bool { func qualifiedFromGroupStage() -> Bool {
groupStagePosition != nil && bracketPosition != nil groupStagePosition != nil && bracketPosition != nil
} }
@ -304,6 +309,7 @@ class TeamRegistration: ModelObject, Storable {
case _walkOut = "walkOut" case _walkOut = "walkOut"
case _lockWeight = "lockWeight" case _lockWeight = "lockWeight"
case _confirmationDate = "confirmationDate" case _confirmationDate = "confirmationDate"
case _qualified = "qualified"
} }
} }

@ -21,7 +21,7 @@ class TeamScore: ModelObject, Storable {
var walkOut: Int? var walkOut: Int?
var luckyLoser: Bool var luckyLoser: Bool
internal init(match: String, teamRegistration: String? = nil, playerRegistrations: [String]? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Bool) { internal init(match: String, teamRegistration: String? = nil, playerRegistrations: [String]? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Bool = false) {
self.match = match self.match = match
self.teamRegistration = teamRegistration self.teamRegistration = teamRegistration
self.playerRegistrations = playerRegistrations self.playerRegistrations = playerRegistrations
@ -30,6 +30,10 @@ class TeamScore: ModelObject, Storable {
self.luckyLoser = luckyLoser self.luckyLoser = luckyLoser
} }
func isWalkOut() -> Bool {
walkOut != nil
}
func matchObject() -> Match? { func matchObject() -> Match? {
Store.main.findById(match) Store.main.findById(match)
} }
@ -39,7 +43,6 @@ class TeamScore: ModelObject, Storable {
return nil return nil
} }
return DataStore.shared.teamRegistrations.findById(teamRegistration) return DataStore.shared.teamRegistrations.findById(teamRegistration)
} }
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {

@ -461,11 +461,11 @@ class Tournament : ModelObject, Storable {
let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate)
await unsortedPlayers().concurrentForEach { player in try await unsortedPlayers().concurrentForEach { player in
let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate }) let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate })
let sources = dataURLs.map { CSVParser(url: $0) } let sources = dataURLs.map { CSVParser(url: $0) }
try? await player.updateRank(from: sources, lastRank: (player.sex == 0 ? lastRankWoman : lastRankMan) ?? 0) try await player.updateRank(from: sources, lastRank: (player.sex == 0 ? lastRankWoman : lastRankMan) ?? 0)
} }
await MainActor.run { await MainActor.run {
@ -505,7 +505,7 @@ class Tournament : ModelObject, Storable {
func qualifiedTeams() -> [TeamRegistration] { func qualifiedTeams() -> [TeamRegistration] {
unsortedTeams().filter({ $0.qualified() }) unsortedTeams().filter({ $0.qualifiedFromGroupStage() })
} }
func moreQualifiedToDraw() -> Int { func moreQualifiedToDraw() -> Int {
@ -517,7 +517,7 @@ class Tournament : ModelObject, Storable {
return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in
groupStage.teams()[qualifiedPerGroupStage] groupStage.teams()[qualifiedPerGroupStage]
} }
.filter({ $0.qualified() == false }) .filter({ $0.qualifiedFromGroupStage() == false })
} else { } else {
return [] return []
} }
@ -546,11 +546,11 @@ class Tournament : ModelObject, Storable {
let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }) let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false })
if ongoingGroupStages.isEmpty == false { if ongoingGroupStages.isEmpty == false {
return "Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { $0.index.formatted() }.joined(separator: ", ") + " en cours" return "Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours"
} }
return groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix return groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix
} else { } else {
return "Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { $0.index.formatted() }.joined(separator: ", ") + " en cours" return "Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours"
} }
} }

@ -7,6 +7,13 @@
import Foundation import Foundation
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension Sequence { extension Sequence {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] { func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { a, b in return sorted { a, b in
@ -23,16 +30,18 @@ extension Sequence {
extension Sequence { extension Sequence {
func concurrentForEach( func concurrentForEach(
_ operation: @escaping (Element) async -> Void _ operation: @escaping (Element) async throws -> Void
) async { ) async throws {
// A task group automatically waits for all of its // A task group automatically waits for all of its
// sub-tasks to complete, while also performing those // sub-tasks to complete, while also performing those
// tasks in parallel: // tasks in parallel:
await withTaskGroup(of: Void.self) { group in try await withThrowingTaskGroup(of: Void.self) { group in
for element in self { for element in self {
group.addTask { group.addTask {
await operation(element) try await operation(element)
} }
for try await _ in group {}
} }
} }
} }

@ -92,7 +92,7 @@ class NetworkFederalService {
} }
func getClubFederalTournaments(page: Int, tournaments: [FederalTournament], club: String, codeClub: String, startDate: Date? = nil) async -> [FederalTournament] { func getClubFederalTournaments(page: Int, tournaments: [FederalTournament], club: String, codeClub: String, startDate: Date? = nil) async throws -> [FederalTournament] {
if formId.isEmpty { if formId.isEmpty {
do { do {
@ -128,24 +128,28 @@ recherche_type=club&club[autocomplete][value_container][value_field]=\(codeClub.
request.httpMethod = "POST" request.httpMethod = "POST"
request.httpBody = postData request.httpBody = postData
do {
let commands : [HttpCommand] = try await runTenupTask(request: request) let commands : [HttpCommand] = try await runTenupTask(request: request)
if commands.anySatisfy({ $0.command == "alert" }) {
throw NetworkManagerError.maintenance
}
let resultCommand = commands.first(where: { $0.results != nil }) let resultCommand = commands.first(where: { $0.results != nil })
if let gatheredTournaments = resultCommand?.results?.items { if let gatheredTournaments = resultCommand?.results?.items {
var finalTournaments = tournaments + gatheredTournaments var finalTournaments = tournaments + gatheredTournaments
if let count = resultCommand?.results?.nb_results { if let count = resultCommand?.results?.nb_results {
if finalTournaments.count < count { if finalTournaments.count < count {
let newTournaments = await getClubFederalTournaments(page: page+1, tournaments: finalTournaments, club: club, codeClub: codeClub) let newTournaments = try await getClubFederalTournaments(page: page+1, tournaments: finalTournaments, club: club, codeClub: codeClub)
finalTournaments = finalTournaments + newTournaments finalTournaments = finalTournaments + newTournaments
} }
} }
return finalTournaments return finalTournaments
} }
} catch {
print("getClubFederalTournaments", error)
}
// do {
// } catch {
// print("getClubFederalTournaments", error)
// }
//
return [] return []
} }

@ -14,4 +14,13 @@ enum NetworkManagerError: LocalizedError {
case mailNotSent //no network no error case mailNotSent //no network no error
case messageFailed case messageFailed
case messageNotSent //no network no error case messageNotSent //no network no error
var errorDescription: String? {
switch self {
case .maintenance:
return "Le site de la FFT est en maintenance"
default:
return String(describing: self)
}
}
} }

@ -972,6 +972,10 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable {
self.init(rawValue: value) self.init(rawValue: value)
} }
func defaultWalkOutScore(_ asWalkOutTeam: Bool) -> [Int] {
Array(repeating: asWalkOutTeam ? 0 : setFormat.scoreToWin, count: setsToWin)
}
var weight: Int { var weight: Int {
switch self { switch self {
case .twoSets, .twoSetsDecisivePoint: case .twoSets, .twoSetsDecisivePoint:

@ -78,7 +78,7 @@ class SourceFileManager {
allFiles.contains(where: { $0.dateFromPath == date }) == false allFiles.contains(where: { $0.dateFromPath == date }) == false
} }
await dates.concurrentForEach { date in try? await dates.concurrentForEach { date in
await self.fetchData(fromDate: date) await self.fetchData(fromDate: date)
} }
} }

@ -0,0 +1,99 @@
//
// MatchDescriptor.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import Foundation
class MatchDescriptor: ObservableObject {
@Published var matchFormat: MatchFormat
@Published var setDescriptors: [SetDescriptor]
var court: Int = 1
var title: String = "Titre du match"
var teamLabelOne: String = ""
var teamLabelTwo: String = ""
var startDate: Date = Date()
var match: Match?
init(match: Match? = nil) {
self.match = match
if let groupStage = match?.groupStageObject {
self.matchFormat = groupStage.matchFormat
self.setDescriptors = [SetDescriptor(setFormat: groupStage.matchFormat.setFormat)]
} else {
let format = match?.matchFormat ?? match?.currentTournament()?.matchFormat ?? .defaultFormatForMatchType(.groupStage)
self.matchFormat = format
self.setDescriptors = [SetDescriptor(setFormat: format.setFormat)]
}
self.teamLabelOne = match?.team(.one)?.teamLabel() ?? ""
self.teamLabelTwo = match?.team(.two)?.teamLabel() ?? ""
if let match, let scoresTeamOne = match.teamScore(.one)?.score, let scoresTeamTwo = match.teamScore(.two)?.score {
self.setDescriptors = combineArraysIntoTuples(scoresTeamOne.components(separatedBy: ","), scoresTeamTwo.components(separatedBy: ",")).map({ (a:String?, b:String?) in
SetDescriptor(valueTeamOne: a != nil ? Int(a!) : nil, valueTeamTwo: b != nil ? Int(b!) : nil, setFormat: match.matchFormat.setFormat)
})
}
}
var teamOneScores: [String] {
setDescriptors.compactMap { $0.valueTeamOne }.map { "\($0)" }
}
var teamTwoScores: [String] {
setDescriptors.compactMap { $0.valueTeamTwo }.map { "\($0)" }
}
var scoreTeamOne: Int { setDescriptors.compactMap { $0.winner }.filter { $0 == .one }.count }
var scoreTeamTwo: Int { setDescriptors.compactMap { $0.winner }.filter { $0 == .two }.count }
var hasEnded: Bool {
return matchFormat.hasEnded(scoreTeamOne: scoreTeamOne, scoreTeamTwo: scoreTeamTwo)
}
func addNewSet() {
if hasEnded == false {
setDescriptors.append(SetDescriptor(setFormat: matchFormat.newSetFormat(setCount: setDescriptors.count)))
}
}
var winner: TeamData {
matchFormat.winner(scoreTeamOne: scoreTeamOne, scoreTeamTwo: scoreTeamTwo)
}
var winnerLabel: String {
if winner == .one {
return teamLabelOne
} else {
return teamLabelTwo
}
}
}
fileprivate func combineArraysIntoTuples(_ array1: [String], _ array2: [String]) -> [(String?, String?)] {
// Zip the two arrays together and map them to tuples of optional strings
let combined = zip(array1, array2).map { (element1, element2) in
return (element1, element2)
}
// If one array is longer than the other, append the remaining elements
let remainingElements: [(String?, String?)]
if array1.count > array2.count {
let remaining = Array(array1[array2.count...]).map { (element) in
return (element, nil as String?)
}
remainingElements = remaining
} else if array2.count > array1.count {
let remaining = Array(array2[array1.count...]).map { (element) in
return (nil as String?, element)
}
remainingElements = remaining
} else {
remainingElements = []
}
// Concatenate the two arrays
return combined + remainingElements
}

@ -0,0 +1,33 @@
//
// SetDescriptor.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import Foundation
struct SetDescriptor: Identifiable, Equatable {
let id: UUID = UUID()
var valueTeamOne: Int?
var valueTeamTwo: Int?
var tieBreakValueTeamOne: Int?
var tieBreakValueTeamTwo: Int?
var setFormat: SetFormat
var hasEnded: Bool {
if let valueTeamTwo, let valueTeamOne {
return setFormat.hasEnded(teamOne: valueTeamOne, teamTwo: valueTeamTwo)
} else {
return false
}
}
var winner: TeamData? {
if let valueTeamTwo, let valueTeamOne {
return setFormat.winner(teamOne: valueTeamOne, teamTwo: valueTeamTwo)
} else {
return nil
}
}
}

@ -130,7 +130,7 @@ struct ClubSearchView: View {
} description: { } description: {
Text("Une erreur est survenue lors de la récupération de votre localisation.") Text("Une erreur est survenue lors de la récupération de votre localisation.")
} actions: { } actions: {
RowButtonView(title: "D'accord") { RowButtonView("D'accord") {
locationManager.lastError = nil locationManager.lastError = nil
} }
} }
@ -147,7 +147,7 @@ struct ClubSearchView: View {
Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.") Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.")
} actions: { } actions: {
if locationManager.manager.authorizationStatus != .restricted { if locationManager.manager.authorizationStatus != .restricted {
RowButtonView(title: "Chercher autour de moi") { RowButtonView("Chercher autour de moi") {
if locationManager.manager.authorizationStatus == .notDetermined { if locationManager.manager.authorizationStatus == .notDetermined {
locationManager.manager.requestWhenInUseAuthorization() locationManager.manager.requestWhenInUseAuthorization()
} else if locationManager.manager.authorizationStatus == .denied { } else if locationManager.manager.authorizationStatus == .denied {
@ -157,7 +157,7 @@ struct ClubSearchView: View {
} }
} }
} }
RowButtonView(title: "Chercher une ville ou un code postal") { RowButtonView("Chercher une ville ou un code postal") {
searchPresented = true searchPresented = true
} }
} }

@ -60,10 +60,10 @@ struct ClubsView: View {
} description: { } description: {
Text("Texte décrivant l'utilité d'un club et les features que cela apporte") Text("Texte décrivant l'utilité d'un club et les features que cela apporte")
} actions: { } actions: {
RowButtonView(title: "Créer un nouveau club", systemImage: "plus.circle.fill") { RowButtonView("Créer un nouveau club", systemImage: "plus.circle.fill") {
presentClubCreationView = true presentClubCreationView = true
} }
RowButtonView(title: "Chercher un club", systemImage: "magnifyingglass.circle.fill") { RowButtonView("Chercher un club", systemImage: "magnifyingglass.circle.fill") {
presentClubSearchView = true presentClubSearchView = true
} }
} }

@ -26,7 +26,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.background { .background {
Circle() Circle()
.fill(Color.white) .fill(Color.white)
.opacity(selectedDestination == nil ? 1.0 : 0.5) .opacity(selectedDestination == nil ? 1.0 : 0.4)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }
@ -41,7 +41,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.background { .background {
Capsule() Capsule()
.fill(Color.white) .fill(Color.white)
.opacity(selectedDestination?.id == destination.id ? 1.0 : 0.5) .opacity(selectedDestination?.id == destination.id ? 1.0 : 0.4)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.overlay(alignment: .bottomTrailing) { .overlay(alignment: .bottomTrailing) {

@ -0,0 +1,45 @@
//
// MatchListView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/04/2024.
//
import SwiftUI
struct MatchListView: View {
@EnvironmentObject var dataStore: DataStore
let section: String
let matches: [Match]
var matchViewStyle: MatchViewStyle = .sectionedStandardStyle
@State private var isExpanded: Bool = true
@ViewBuilder
var body: some View {
if matches.isEmpty == false {
Section {
if isExpanded {
ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: matchViewStyle)
}
}
} header: {
Button {
isExpanded.toggle()
} label: {
HStack {
Text(section.capitalized)
Spacer()
Text(matches.count.formatted())
Image(systemName: isExpanded ? "chevron.down.circle" : "chevron.right.circle")
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity)
}
.headerProminence(.increased)
}
}
}

@ -7,16 +7,35 @@
import SwiftUI import SwiftUI
fileprivate let defaultConfirmationMessage = "Êtes-vous sûr de vouloir faire cela ?"
struct RowButtonView: View { struct RowButtonView: View {
var role: ButtonRole? = nil
let title: String let title: String
var systemImage: String? = nil var systemImage: String? = nil
var image: String? = nil var image: String? = nil
var animatedProgress: Bool = false var animatedProgress: Bool = false
let confirmationMessage: String
let action: () -> () let action: () -> ()
@State private var askConfirmation: Bool = false
init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, image: String? = nil, animatedProgress: Bool = false, confirmationMessage: String? = nil, action: @escaping () -> Void) {
self.role = role
self.title = title
self.systemImage = systemImage
self.image = image
self.animatedProgress = animatedProgress
self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage
self.action = action
}
var body: some View { var body: some View {
Button { Button(role: role) {
if role == .destructive {
askConfirmation = true
} else {
action() action()
}
} label: { } label: {
HStack { HStack {
if animatedProgress { if animatedProgress {
@ -47,8 +66,19 @@ struct RowButtonView: View {
.disabled(animatedProgress) .disabled(animatedProgress)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.tint(.launchScreenBackground) .tint(role == .destructive ? Color.red : Color.launchScreenBackground)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(.zero)) .listRowInsets(EdgeInsets(.zero))
.confirmationDialog("Confirmation",
isPresented: $askConfirmation,
titleVisibility: .visible) {
Button("OK") {
action()
}
Button("Annuler", role: .cancel) {}
} message: {
Text(confirmationMessage)
}
} }
} }

@ -88,7 +88,7 @@ struct EventCreationView: View {
} }
Section { Section {
RowButtonView(title:"Valider") { RowButtonView("Valider") {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil { if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil {
let event = Event(name: eventName) let event = Event(name: eventName)
event.club = selectedClub?.id event.club = selectedClub?.id
@ -143,7 +143,7 @@ struct EventCreationView: View {
} }
Section { Section {
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") { RowButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance() let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament) self.tournaments.append(tournament)
} }

@ -38,7 +38,7 @@ struct GroupStageSettingsView: View {
// if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) { // if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) {
// Section { // Section {
// RowButtonView(title: "Reconstruire les poules") { // RowButtonView("Reconstruire les poules") {
// confirmGroupStageRebuild = true // confirmGroupStageRebuild = true
// } // }
// .modify { // .modify {
@ -83,7 +83,7 @@ struct GroupStageSettingsView: View {
// if tournament.isRoundSwissTournament() == false && (tournament.orderedGroupStages.allSatisfy({ $0.orderedMatches.count > 0 }) == false && tournament.groupStagesAreOver == false && tournament.groupStagesCount > 0) { // if tournament.isRoundSwissTournament() == false && (tournament.orderedGroupStages.allSatisfy({ $0.orderedMatches.count > 0 }) == false && tournament.groupStagesAreOver == false && tournament.groupStagesCount > 0) {
// Section { // Section {
// RowButtonView(title: "Générer les matchs de poules") { // RowButtonView("Générer les matchs de poules") {
// startAllGroupStageConfirmation = true // startAllGroupStageConfirmation = true
// } // }
// .modify { // .modify {

@ -0,0 +1,107 @@
//
// GroupStageTeamView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/04/2024.
//
import SwiftUI
struct GroupStageTeamView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(\.dismiss) private var dismiss
let groupStage: GroupStage
var team: TeamRegistration
var body: some View {
List {
ForEach(team.players()) { player in
Section {
ImportedPlayerView(player: player)
} footer: {
HStack {
Button("contacter") {
}
Spacer()
Button {
player.hasArrived.toggle()
try? dataStore.playerRegistrations.addOrUpdate(instance: player)
} label: {
Label("présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle")
}
}
}
}
if groupStage.tournamentObject()?.hasEnded() == false {
Section {
if team.qualified == false {
RowButtonView("Qualifier l'équipe") {
team.qualified = true
team.bracketPosition = nil
_save()
}
}
}
Section {
if team.qualified {
RowButtonView("Annuler la qualification", role: .destructive) {
team.qualified = false
team.bracketPosition = nil
_save()
}
}
}
Section {
// if let deltaLabel = bracket.tournament?.deltaLabel(index, bracketIndex: bracket.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.resetBracketPosition()
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text("Toute l'équipe, poids: " + entrant.updatedRank.formatted())
// }
// }
//
// ForEach(entrant.orderedPlayers) { player in
// if let deltaLabel = bracket.tournament?.playerDeltaLabel(rank: entrant.otherPlayer(player)?.currentRank, positionInBracket: index, bracketIndex: bracket.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.team?.removeFromPlayers(player)
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text(player.longLabel + ", rang: " + player.formattedRank)
// }
// }
// }
} header: {
Text("Remplacement")
}
Section {
RowButtonView("Retirer de la poule") {
team.groupStagePosition = nil
team.groupStage = nil
_save()
}
}
}
}
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Détail de l'équipe")
}
private func _save() {
try? dataStore.teamRegistrations.addOrUpdate(instance: team)
dismiss()
}
}

@ -8,77 +8,121 @@
import SwiftUI import SwiftUI
struct GroupStageView: View { struct GroupStageView: View {
@EnvironmentObject var dataStore: DataStore
@Bindable var groupStage: GroupStage @Bindable var groupStage: GroupStage
// @State private var selectedMenuLink: MenuLink?
// @State private var canUpdateTournament: Bool = false
// @AppStorage("showLongLabel") private var showLongLabel: Bool = false
// @AppStorage("hideRank") private var hideRank: Bool = false
@State private var confirmGroupStageStart: Bool = false @State private var confirmGroupStageStart: Bool = false
@State private var sortingMode: GroupStageSortingMode = .auto
@State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false
enum MenuLink: Int, Identifiable, Hashable { private enum GroupStageSortingMode {
var id: Int { self.rawValue } case auto
case prepare case score
case weight
} }
var groupStageView: some View { var sortByScore: Bool {
ForEach(0..<(groupStage.size), id: \.self) { index in sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score
// let entrant : Entrant? = runningGroupStageOrderedByScore ? groupStage.orderedByScore[Int(index)] : groupStage.entrantAtIndex(Int(index)) }
if let team = groupStage.teamsAt(index) {
Text(team.teamLabel()) func teamAt(atIndex index: Int) -> TeamRegistration? {
// GroupStageEntrantMenuView(entrant: entrant, groupStage: groupStage, index: index.intValue) sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index)
}
var body: some View {
List {
Section {
_groupStageView()
} header: {
HStack {
if let startDate = groupStage.startDate {
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute()))
}
Spacer()
Button {
if sortingMode == .weight {
sortingMode = .score
} else { } else {
Menu { sortingMode = .weight
// Section { }
// EntrantPickerView(groupStage: groupStage, index: Int(index))
// }
//
// if let tournament = groupStage.tournament, let deltaLabel = tournament.deltaLabel(index.intValue, groupStageIndex: groupStage.index.intValue) {
// let date = tournament.localizedDate ?? ""
// Divider()
// Section {
// ShareLink(item: "\(tournament.localizedTitle)\n\(date)\nCherche une équipe dont le poids d'équipe " + deltaLabel) {
// Text(deltaLabel)
// }
// } header: {
// Text("Remplacer avec un poids d'équipe")
// }
// }
} label: { } label: {
HStack { Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly)
Text("#\(index+1)") }
Text("Aucune équipe")
} }
.buttonStyle(.plain)
} }
MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID())
MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID())
MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID())
MatchListView(section: "terminés", matches: groupStage.finishedMatches()).id(UUID())
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
_groupStageMenuView()
} }
} }
} }
var body: some View { private func _groupStageView() -> some View {
List { ForEach(0..<(groupStage.size), id: \.self) { index in
Section { if let team = teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition {
groupStageView NavigationLink {
// .disabled(canUpdateTournament == false) GroupStageTeamView(groupStage: groupStage, team: team)
// .sheet(item: $selectedMenuLink) { selectedMenuLink in } label: {
// switch selectedMenuLink { HStack(alignment: .center) {
// case .prepare: VStack(alignment: .leading, spacing: 0) {
// PrepareGroupStageView(groupStage: groupStage)
// }
// }
} header: {
HStack { HStack {
if groupStage.isBroadcasted() { Text("#\(groupStagePosition + 1)")
Label(groupStage.groupStageTitle(), systemImage: "airplayvideo") Text("Poids \(team.weight)")
}
.font(.caption)
HStack {
if let teamName = team.name {
Text(teamName)
} else { } else {
Text(groupStage.groupStageTitle()) VStack(alignment: .leading) {
ForEach(team.players()) { player in
Text(player.playerLabel())
}
}
}
if team.qualified {
Image(systemName: "checkmark.seal")
}
}
} }
Spacer() Spacer()
if let startDate = groupStage.startDate { if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) {
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute())) Text(score)
}
} }
} }
} footer: { } else {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 0) {
HStack { HStack {
if groupStage.matches.isEmpty { Text("#\(index + 1)")
}
.font(.caption)
TeamPickerView(teamPicked: { team in
print(team.pasteData())
team.groupStage = groupStage.id
team.groupStagePosition = index
try? dataStore.teamRegistrations.addOrUpdate(instance: team)
})
}
}
}
}
}
private func _groupStageMenuView() -> some View {
Menu {
if groupStage.matches().isEmpty {
Button { Button {
//groupStage.startGroupStage() //groupStage.startGroupStage()
//save() //save()
@ -87,8 +131,14 @@ struct GroupStageView: View {
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
} }
Spacer()
Menu { Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true
}
Button("Recommencer tous les matchs", role: .destructive) {
confirmResetMatch = true
}
// Button { // Button {
// selectedMenuLink = .prepare // selectedMenuLink = .prepare
// } label: { // } label: {
@ -184,34 +234,25 @@ struct GroupStageView: View {
// Text("Éditer") // Text("Éditer")
// } // }
} label: { } label: {
HStack { LabelOptions()
Spacer()
Label("Options", systemImage: "ellipsis.circle").labelStyle(.titleOnly)
}
} }
.buttonStyle(.borderless) .confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmRemoveAll, titleVisibility: .visible) {
Button("Oui") {
let teams = groupStage.teams()
teams.forEach { team in
team.groupStagePosition = nil
team.groupStage = nil
} }
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} }
if groupStage.matches.isEmpty == false {
Section {
ForEach(groupStage.matches) { match in
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
} }
} header: { .confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) {
Text("Matchs de la " + groupStage.groupStageTitle()) Button("Oui") {
groupStage.buildMatches()
} }
} }
} }
// .onAppear {
// if let tournament = groupStage.tournament {
// canUpdateTournament = PersistenceController.shared.container.canUpdateRecord(forManagedObjectWith: tournament.objectID)
// } else {
// canUpdateTournament = true
// }
// }
}
// func save() { // func save() {
// do { // do {
@ -228,163 +269,3 @@ struct GroupStageView: View {
// } // }
// } // }
} }
//struct GroupStageEntrantMenuView: View {
// @ObservedObject var entrant: Entrant
// @ObservedObject var groupStage: GroupStage
// @Environment(\.managedObjectContext) private var viewContext
// @AppStorage("showLongLabel") private var showLongLabel: Bool = false
// @AppStorage("hideRank") private var hideRank: Bool = false
//
// let index: Int
//
// var body: some View {
// Menu {
// ForEach(entrant.orderedPlayers) { player in
// Menu {
// Text(player.formattedRank)
// Text(player.localizedAge)
// if let computedClubName = player.computedClubName {
// Text(computedClubName)
// }
// } label: {
// Text(player.longLabel)
// }
// }
//
// if groupStage.tournament?.isOver == false {
// if entrant.qualified == false {
// Divider()
// Button {
// entrant.addToQualifiedGroup()
// entrant.objectWillChange.send()
// entrant.orderedGroupStages.forEach { $0.objectWillChange.send() }
// entrant.currentTournament?.objectWillChange.send()
// entrant.currentTournament?.orderedMatches.forEach { $0.objectWillChange.send() }
// save()
// } label: {
// Label("Qualifier l'équipe", systemImage: "checkmark")
// }
// }
//
// Divider()
// if entrant.qualified {
// Menu {
// Button(role: .destructive) {
// entrant.unqualified()
// entrant.objectWillChange.send()
// entrant.orderedGroupStages.forEach { $0.objectWillChange.send() }
// entrant.currentTournament?.objectWillChange.send()
// entrant.currentTournament?.orderedMatches.forEach { $0.objectWillChange.send() }
// save()
// } label: {
// Label("Annuler la qualification", systemImage: "xmark")
// }
// } label: {
// Text("Qualification")
// }
// }
//
// Menu {
// if let deltaLabel = groupStage.tournament?.deltaLabel(index, groupStageIndex: groupStage.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.resetGroupStagePosition()
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text("Toute l'équipe, poids: " + entrant.updatedRank.formatted())
// }
// }
//
// ForEach(entrant.orderedPlayers) { player in
// if let deltaLabel = groupStage.tournament?.playerDeltaLabel(rank: entrant.otherPlayer(player)?.currentRank, positionInGroupStage: index, groupStageIndex: groupStage.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.team?.removeFromPlayers(player)
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text(player.longLabel + ", rang: " + player.formattedRank)
// }
// }
// }
// } label: {
// Text("Remplacement")
// }
//
// Menu {
// Button(role: .destructive) {
// entrant.resetGroupStagePosition()
// save()
// } label: {
// Label("Retirer l'équipe", systemImage: "xmark")
// }
// } label: {
// Text("Retirer")
// }
//
// }
// } label: {
// HStack(alignment: .center) {
// if let tournament = groupStage.tournament, groupStage.hasEnded, groupStage.groupStageRound > 0 {
// Text("#\(index + Int((groupStage.index - tournament.numberOfGroupStages)*tournament.teamsPerGroupStage) + 1)")
// } else {
// Text("#\(index + 1)")
// }
// VStack(alignment: .leading, spacing: 0) {
// if hideRank == false {
// Text("Poids \(entrant.updatedRank)")
// .font(.caption)
// }
//
// HStack {
// if let brand = entrant.team?.brand?.title {
// Text(brand)
// } else {
//
// VStack(alignment: .leading) {
// Text(entrant.longLabelPlayerOne)
// Text(entrant.longLabelPlayerTwo)
// }
//
// }
//
// if groupStage.tournament?.isRoundSwissTournament() == true {
// if entrant.groupStagePosition == groupStage.index {
// Text("forcé")
// } else {
// Text("auto")
// }
// } else {
// if entrant.qualified {
// Image(systemName: "checkmark.seal")
// }
// }
// }
// }
// Spacer()
// Text(groupStage.scoreLabel(for: entrant.position(in: groupStage)))
// }
// }
// .buttonStyle(.plain)
// }
//
// func save() {
// do {
// try viewContext.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nsError = error as NSError
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
// }
// }
//}

@ -9,23 +9,78 @@ import SwiftUI
struct GroupStagesView: View { struct GroupStagesView: View {
var tournament: Tournament var tournament: Tournament
@State private var selectedGroupStage: GroupStage? @State private var selectedDestination: GroupStageDestination?
enum GroupStageDestination: Selectable, Identifiable {
case all
case groupStage(GroupStage)
var id: String {
switch self {
case .all:
return "all-group-stage"
case .groupStage(let groupStage):
return groupStage.id
}
}
func selectionLabel() -> String {
switch self {
case .all:
return "Tout"
case .groupStage(let groupStage):
return groupStage.groupStageTitle()
}
}
func badgeValue() -> Int? {
switch self {
case .all:
return nil
case .groupStage(let groupStage):
return groupStage.badgeValue()
}
}
}
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
_selectedGroupStage = State(wrappedValue: tournament.getActiveGroupStage()) let gs = tournament.getActiveGroupStage()
if let gs {
_selectedDestination = State(wrappedValue: .groupStage(gs))
}
}
func allDestinations() -> [GroupStageDestination] {
var allDestinations : [GroupStageDestination] = [.all]
let groupStageDestinations : [GroupStageDestination] = tournament.groupStages().map { GroupStageDestination.groupStage($0) }
allDestinations.append(contentsOf: groupStageDestinations)
return allDestinations
} }
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedGroupStage, destinations: tournament.groupStages(), nilDestinationIsValid: true) GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedGroupStage { switch selectedDestination {
case .none: case .all:
GroupStageSettingsView() List {
.navigationTitle("Réglages") let allGroupStages = tournament.groupStages()
case .some(let groupStage): let availableToStart = allGroupStages.flatMap({ $0.availableToStart() })
let runningMatches = allGroupStages.flatMap({ $0.runningMatches() })
let readyMatches = allGroupStages.flatMap({ $0.readyMatches() })
let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() })
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle)
}
.navigationTitle("Toutes les poules")
case .groupStage(let groupStage):
GroupStageView(groupStage: groupStage) GroupStageView(groupStage: groupStage)
.navigationTitle(groupStage.groupStageTitle()) .navigationTitle(groupStage.groupStageTitle())
case nil:
GroupStageSettingsView()
.navigationTitle("Réglages")
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

@ -83,7 +83,7 @@ struct MatchDateView: View {
.monospacedDigit() .monospacedDigit()
} }
if match.startDate == nil { if match.startDate == nil && match.hasEnded() == false {
Text("démarrage").font(.footnote).foregroundStyle(.secondary) Text("démarrage").font(.footnote).foregroundStyle(.secondary)
Text("non défini") Text("non défini")
} }

@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
struct MatchDetailView: View { struct MatchDetailView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
let matchViewStyle: MatchViewStyle let matchViewStyle: MatchViewStyle
@ -202,11 +203,14 @@ struct MatchDetailView: View {
// } // }
// .presentationDetents([.fraction(0.66)]) // .presentationDetents([.fraction(0.66)])
// } // }
// .sheet(item: $scoreType, onDismiss: { .sheet(item: $scoreType, onDismiss: {
// if match.hasEnded() && match.isTournamentMatch() { if match.hasEnded() && match.isTournamentMatch() {
// dismiss() dismiss()
// } }
// }) { scoreType in }) { scoreType in
let matchDescriptor = MatchDescriptor(match: match)
EditScoreView(matchDescriptor: matchDescriptor)
// switch scoreType { // switch scoreType {
// case .edition: // case .edition:
// let matchDescriptor = MatchDescriptor(match: match) // let matchDescriptor = MatchDescriptor(match: match)
@ -238,8 +242,8 @@ struct MatchDetailView: View {
// FeedbackView(feedbackData: feedbackData) // FeedbackView(feedbackData: feedbackData)
// } // }
// } // }
//
// } }
// .refreshable { // .refreshable {
// if match.isBroadcasted() { // if match.isBroadcasted() {
@ -308,24 +312,6 @@ struct MatchDetailView: View {
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
} }
enum MatchDateSetup: Hashable, Identifiable {
case inMinutes(Int)
case now
case customDate
var id: Int { hashValue }
}
enum MatchFieldSetup: Hashable, Identifiable {
case random
// case firstAvailable
case field(String)
var id: Int { hashValue }
}
enum ScoreType: Int, Identifiable, Hashable { enum ScoreType: Int, Identifiable, Hashable {
var id: Int { var id: Int {
self.rawValue self.rawValue
@ -338,14 +324,6 @@ struct MatchDetailView: View {
case health = 5 case health = 5
} }
var entrantLabelOne: String {
return "match.longLabelTeamOne"
}
var entrantLabelTwo: String {
return "match.longLabelTeamTwo"
}
@ViewBuilder @ViewBuilder
var menuView: some View { var menuView: some View {
if match.isReady() { if match.isReady() {
@ -374,7 +352,7 @@ struct MatchDetailView: View {
} }
var inputScoreView: some View { var inputScoreView: some View {
RowButtonView(title: "Saisir les résultats", systemImage: "list.clipboard") { RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
scoreType = .edition scoreType = .edition
} }
} }
@ -461,26 +439,8 @@ struct MatchDetailView: View {
// } // }
// } // }
RowButtonView(title: "Valider") { RowButtonView("Valider") {
if match.hasEnded() == false { match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
match.startDate = startDate
if match.isTournamentMatch() {
// switch fieldSetup {
// case .random:
// let field = match.freeFields.randomElement() ?? match.currentTournament?.freeFields.randomElement() ?? 1
// match.setupFieldAndStartDateIfPossible(field)
// case .field(let courtIndex):
// let fieldIndex = Int64(courtIndex)
// match.setupFieldAndStartDateIfPossible(fieldIndex)
// }
}
} else {
match.startDate = startDate
if match.endDate != nil {
match.endDate = endDate
}
}
if broadcasted { if broadcasted {
broadcastAndSave() broadcastAndSave()
@ -501,12 +461,12 @@ struct MatchDetailView: View {
var broadcastView: some View { var broadcastView: some View {
Section { Section {
// if match.isBroadcasted() { // if match.isBroadcasted() {
// RowButtonView(title: "Arrêter de diffuser") { // RowButtonView("Arrêter de diffuser") {
// match.stopBroadcast() // match.stopBroadcast()
// save() // save()
// } // }
// } else if match.canBroadcast() == true { // } else if match.canBroadcast() == true {
// RowButtonView(title: "Diffuser", systemImage: "airplayvideo") { // RowButtonView("Diffuser", systemImage: "airplayvideo") {
// broadcastAndSave() // broadcastAndSave()
// } // }
// } // }
@ -523,6 +483,7 @@ struct MatchDetailView: View {
private func save() { private func save() {
try? dataStore.matches.addOrUpdate(instance: match)
} }
private func broadcastAndSave() { private func broadcastAndSave() {

@ -61,9 +61,7 @@ struct MatchSummaryView: View {
if let groupStage = match.groupStageObject, matchViewStyle == .standardStyle { if let groupStage = match.groupStageObject, matchViewStyle == .standardStyle {
Text(groupStage.groupStageTitle()) Text(groupStage.groupStageTitle())
} }
// if let index = match.entrantOne()?.bracketPositions?.first, let index2 = match.entrantTwo()?.bracketPositions?.first { Text(match.matchTitle())
// Text("#\(index) contre #\(index2)")
// }
} else if let currentTournament = match.currentTournament() { } else if let currentTournament = match.currentTournament() {
if matchViewStyle == .feedStyle { if matchViewStyle == .feedStyle {
//tournamentHeaderView(currentTournament) //tournamentHeaderView(currentTournament)

@ -20,6 +20,7 @@ struct ActivityView: View {
@State private var federalTournaments: [FederalTournament] = [] @State private var federalTournaments: [FederalTournament] = []
@State private var isGatheringFederalTournaments: Bool = false @State private var isGatheringFederalTournaments: Bool = false
@Binding var selectedTab: TabDestination? @Binding var selectedTab: TabDestination?
@State private var error: Error?
var runningTournaments: [FederalTournamentHolder] { var runningTournaments: [FederalTournamentHolder] {
dataStore.tournaments.filter({ $0.endDate == nil }) dataStore.tournaments.filter({ $0.endDate == nil })
@ -65,7 +66,17 @@ struct ActivityView: View {
} }
} }
.overlay { .overlay {
if isGatheringFederalTournaments { if let error, agendaDestination == .tenup {
ContentUnavailableView {
Label("Erreur", systemImage: "exclamationmark")
} description: {
Text(error.localizedDescription)
} actions: {
RowButtonView("D'accord.") {
self.error = nil
}
}
} else if isGatheringFederalTournaments {
ProgressView() ProgressView()
} else { } else {
if tournaments.isEmpty { if tournaments.isEmpty {
@ -77,7 +88,7 @@ struct ActivityView: View {
} description: { } description: {
Text("Description du filtre") Text("Description du filtre")
} actions: { } actions: {
RowButtonView(title: "supprimer le filtre") { RowButtonView("supprimer le filtre") {
filterEnabled.toggle() filterEnabled.toggle()
} }
} }
@ -182,8 +193,12 @@ struct ActivityView: View {
private func _gatherFederalTournaments() { private func _gatherFederalTournaments() {
isGatheringFederalTournaments = true isGatheringFederalTournaments = true
Task { Task {
await dataStore.clubs.filter { $0.code != nil }.concurrentForEach { club in do {
federalTournaments += await NetworkFederalService.shared.getClubFederalTournaments(page: 0, tournaments: [], club: club.name, codeClub: club.code!, startDate: .now.startOfMonth) try await dataStore.clubs.filter { $0.code != nil }.concurrentForEach { club in
federalTournaments += try await NetworkFederalService.shared.getClubFederalTournaments(page: 0, tournaments: [], club: club.name, codeClub: club.code!, startDate: .now.startOfMonth)
}
} catch {
self.error = error
} }
isGatheringFederalTournaments = false isGatheringFederalTournaments = false
} }
@ -215,10 +230,10 @@ struct ActivityView: View {
} description: { } description: {
Text("Aucun événement en cours ou à venir dans votre agenda.") Text("Aucun événement en cours ou à venir dans votre agenda.")
} actions: { } actions: {
RowButtonView(title: "Créer un nouvel événement") { RowButtonView("Créer un nouvel événement") {
newTournament = Tournament.newEmptyInstance() newTournament = Tournament.newEmptyInstance()
} }
RowButtonView(title: "Importer via Tenup") { RowButtonView("Importer via Tenup") {
agendaDestination = .tenup agendaDestination = .tenup
} }
} }
@ -239,7 +254,7 @@ struct ActivityView: View {
} description: { } description: {
Text("Pour voir vos tournois tenup ici, indiquez vos clubs préférés.") Text("Pour voir vos tournois tenup ici, indiquez vos clubs préférés.")
} actions: { } actions: {
RowButtonView(title: "Choisir mes clubs préférés") { RowButtonView("Choisir mes clubs préférés") {
selectedTab = .umpire selectedTab = .umpire
} }
} }
@ -249,7 +264,7 @@ struct ActivityView: View {
} description: { } description: {
Text("Aucun tournoi n'a pu être récupéré via tenup.") Text("Aucun tournoi n'a pu être récupéré via tenup.")
} actions: { } actions: {
RowButtonView(title: "Rafraîchir") { RowButtonView("Rafraîchir") {
_gatherFederalTournaments() _gatherFederalTournaments()
} }
} }

@ -16,13 +16,13 @@ struct EmptyActivityView: View {
WelcomeView() WelcomeView()
Section { Section {
RowButtonView(title: "Créer votre premier événement", action: { RowButtonView("Créer votre premier événement", action: {
newTournament = Tournament.newEmptyInstance() newTournament = Tournament.newEmptyInstance()
}) })
} }
Section { Section {
RowButtonView(title: "Importer vos tournois Tenup", action: { RowButtonView("Importer vos tournois Tenup", action: {
}) })
} }

@ -132,24 +132,6 @@ struct MainView: View {
} }
} }
fileprivate extension View {
func tabItem(for tabDestination: TabDestination) -> some View {
modifier(TabItemModifier(tabDestination: tabDestination))
}
}
fileprivate struct TabItemModifier: ViewModifier {
let tabDestination: TabDestination
func body(content: Content) -> some View {
content
.tabItem {
Label(tabDestination.title, systemImage: tabDestination.image)
}
.tag(tabDestination as TabDestination?)
}
}
#Preview { #Preview {
MainView() MainView()
} }

@ -54,7 +54,7 @@ struct PadelClubView: View {
// } description: { // } description: {
// Text("Padel peut importer toutes les données publique de la FFT concernant tous les compétiteurs et compétitrices.") // Text("Padel peut importer toutes les données publique de la FFT concernant tous les compétiteurs et compétitrices.")
// } actions: { // } actions: {
// RowButtonView(title: "Démarrer l'importation") { // RowButtonView("Démarrer l'importation") {
// _startImporting() // _startImporting()
// } // }
// } // }

@ -166,7 +166,7 @@ struct PlayerPopoverView: View {
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
Section { Section {
RowButtonView(title: "Valider et ajouter un autre") { RowButtonView("Valider et ajouter un autre") {
createManualPlayer() createManualPlayer()
lastName = "" lastName = ""
firstName = "" firstName = ""

@ -23,14 +23,14 @@ struct RoundSettingsView: View {
Toggle("Éditer les têtes de série", isOn: $isEditingTournamentSeed) Toggle("Éditer les têtes de série", isOn: $isEditingTournamentSeed)
Section { Section {
RowButtonView(title: "Retirer toutes les têtes de séries") { RowButtonView("Retirer toutes les têtes de séries") {
tournament.unsortedTeams().forEach({ $0.bracketPosition = nil }) tournament.unsortedTeams().forEach({ $0.bracketPosition = nil })
} }
} }
Section { Section {
if let lastRound = tournament.rounds().first { // first is final, last round if let lastRound = tournament.rounds().first { // first is final, last round
RowButtonView(title: "Supprimer " + lastRound.roundTitle()) { RowButtonView("Supprimer " + lastRound.roundTitle()) {
try? dataStore.rounds.delete(instance: lastRound) try? dataStore.rounds.delete(instance: lastRound)
} }
} }
@ -38,7 +38,7 @@ struct RoundSettingsView: View {
Section { Section {
let roundIndex = tournament.rounds().count let roundIndex = tournament.rounds().count
RowButtonView(title: "Ajouter " + RoundRule.roundName(fromRoundIndex: roundIndex)) { RowButtonView("Ajouter " + RoundRule.roundName(fromRoundIndex: roundIndex)) {
let round = Round(tournament: tournament.id, index: roundIndex, matchFormat: tournament.matchFormat) let round = Round(tournament: tournament.id, index: roundIndex, matchFormat: tournament.matchFormat)
let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex) let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex) let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
@ -64,7 +64,7 @@ struct RoundSettingsView: View {
if let roundIndex { if let roundIndex {
RowButtonView(title: "Valider") { RowButtonView("Valider") {
if availableSeedGroup == SeedInterval(first: 1, last: 2) { if availableSeedGroup == SeedInterval(first: 1, last: 2) {
let seeds = tournament.seeds() let seeds = tournament.seeds()
// let startIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex) // let startIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)

@ -0,0 +1,104 @@
//
// EditScoreView.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 27/02/2023.
//
import SwiftUI
struct EditScoreView: View {
@EnvironmentObject var dataStore: DataStore
@ObservedObject var matchDescriptor: MatchDescriptor
@Environment(\.dismiss) private var dismiss
func walkout(_ team: TeamData) {
matchDescriptor.match?.setWalkOut(team)
save()
dismiss()
}
var body: some View {
Form {
Section {
Text(matchDescriptor.teamLabelOne)
Text(matchDescriptor.teamLabelTwo)
} footer: {
HStack {
Menu {
Button {
walkout(.one)
} label: {
Text(matchDescriptor.teamLabelOne)
}
Button {
walkout(.two)
} label: {
Text(matchDescriptor.teamLabelTwo)
}
} label: {
Text("Forfait")
}
Spacer()
MatchTypeSmallSelectionView(selectedFormat: $matchDescriptor.matchFormat, format: "Format")
.onChange(of: matchDescriptor.matchFormat) { newValue in
matchDescriptor.setDescriptors.removeAll()
matchDescriptor.addNewSet()
}
}
}
ForEach($matchDescriptor.setDescriptors) { $setDescriptor in
SetInputView(setDescriptor: $setDescriptor)
.onChange(of: setDescriptor.hasEnded) { hasEnded in
if hasEnded {
if matchDescriptor.hasEnded == false {
matchDescriptor.addNewSet()
}
} else {
let index = matchDescriptor.setDescriptors.firstIndex(where: { $0 == setDescriptor }) ?? 0
matchDescriptor.setDescriptors = Array(matchDescriptor.setDescriptors[0...index])
}
}
}
if matchDescriptor.hasEnded {
Section {
HStack {
Spacer()
VStack {
Text(matchDescriptor.winnerLabel)
}
.multilineTextAlignment(.center)
Spacer()
}
RowButtonView("Victoire") {
matchDescriptor.match?.setScore(fromMatchDescriptor: matchDescriptor)
save()
dismiss()
}
} footer: {
Text("Termine la rencontre sur ce score")
}
}
if matchDescriptor.match?.hasEnded() == false {
Section {
RowButtonView("Mise à jour") {
matchDescriptor.match?.updateScore(fromMatchDescriptor: matchDescriptor)
save()
dismiss()
}
} footer: {
Text("Met à jour le score pour la diffusion, ne termine pas la rencontre")
}
}
}
}
func save() {
if let match = matchDescriptor.match {
try? dataStore.matches.addOrUpdate(instance: match)
}
}
}

@ -0,0 +1,50 @@
//
// PointSelectionView.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 27/02/2023.
//
import SwiftUI
struct PointSelectionView: View {
@Binding var valueSelected: Int?
var values: [Int]
var possibleValues: [Int]
var disableValues: [Int] = []
var deleteAction: () -> ()
let gridItems: [GridItem] = [GridItem(.adaptive(minimum: 65), spacing: 20)]
init(valueSelected: Binding<Int?>, values: [Int], possibleValues: [Int], disableValues: [Int], deleteAction: @escaping () -> Void) {
_valueSelected = valueSelected
self.values = values
self.possibleValues = Set(values + possibleValues).sorted().reversed()
self.disableValues = disableValues
self.deleteAction = deleteAction
}
var body: some View {
LazyVGrid(columns: gridItems, alignment: .center, spacing: 20) {
ForEach(possibleValues, id: \.self) { value in
Button {
valueSelected = value
} label: {
PointView(value: "\(value).circle.fill")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(disableValues.contains(value) || values.contains(value) == false )
}
Button {
deleteAction()
} label: {
PointView(value: "delete.left.fill")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
.padding()
}
}

@ -0,0 +1,26 @@
//
// PointView.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 27/02/2023.
//
import SwiftUI
struct PointView: View {
let value: String
var body: some View {
Image(systemName: value)
.resizable()
.aspectRatio(contentMode: .fit)
.font(.largeTitle)
.frame(height: 40)
}
}
struct PointView_Previews: PreviewProvider {
static var previews: some View {
PointView(value:"delete.left.fill")
}
}

@ -0,0 +1,215 @@
//
// SetInputView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import SwiftUI
struct SetInputView: View {
@Binding var setDescriptor: SetDescriptor
@State private var showSetInputView: Bool = true
@State private var showTieBreakInputView: Bool = false
var setFormat: SetFormat { setDescriptor.setFormat }
private var showTieBreakView: Bool {
setFormat.shouldTiebreak(scoreTeamOne: setDescriptor.valueTeamOne ?? 0, scoreTeamTwo: setDescriptor.valueTeamTwo ?? 0)
}
private var isMainViewTieBreakView: Bool {
setFormat == .superTieBreak || setFormat == .megaTieBreak
}
private var currentValue: Binding<Int?> {
Binding {
if setDescriptor.valueTeamOne != nil {
return setDescriptor.valueTeamTwo
} else {
return setDescriptor.valueTeamOne
}
} set: { newValue, _ in
if setDescriptor.valueTeamOne != nil {
setDescriptor.valueTeamTwo = newValue
} else {
setDescriptor.valueTeamOne = newValue
}
}
}
private var currentTiebreakValue: Binding<Int?> {
Binding {
if setDescriptor.tieBreakValueTeamOne != nil {
return setDescriptor.tieBreakValueTeamTwo
} else {
return setDescriptor.tieBreakValueTeamOne
}
} set: { newValue, _ in
if let tieBreakValueTeamOne = setDescriptor.tieBreakValueTeamOne, let tieBreakValueTeamTwo = setDescriptor.tieBreakValueTeamTwo {
if tieBreakValueTeamOne < tieBreakValueTeamTwo && tieBreakValueTeamTwo > 6 {
setDescriptor.tieBreakValueTeamOne = newValue
}
else if tieBreakValueTeamOne > tieBreakValueTeamTwo && tieBreakValueTeamOne > 6 {
setDescriptor.tieBreakValueTeamTwo = newValue
}
}
else if setDescriptor.tieBreakValueTeamOne != nil {
setDescriptor.tieBreakValueTeamTwo = newValue
} else {
setDescriptor.tieBreakValueTeamOne = newValue
}
}
}
private var disableValues: [Int] {
if let valueTeamOne = setDescriptor.valueTeamOne {
return setFormat.disableValuesForTeamTwo(with: valueTeamOne)
}
return []
}
private var disableTieBreakValues: [Int] {
if let tieBreakValueTeamOne = setDescriptor.tieBreakValueTeamOne {
if tieBreakValueTeamOne == 7 {
return [7,6]
}
}
return []
}
func deleteLastValue() {
setDescriptor.valueTeamOne = nil
setDescriptor.valueTeamTwo = nil
}
func deleteLastTiebreakValue() {
setDescriptor.tieBreakValueTeamOne = nil
setDescriptor.tieBreakValueTeamTwo = nil
}
func possibleValues() -> [Int] {
if let valueTeamOne = setDescriptor.valueTeamOne {
if valueTeamOne == 7 && setFormat == .six {
return [6,5]
}
if valueTeamOne == 5 && setFormat == .four {
return [3,2]
}
}
return setFormat.possibleValues
}
func tieBreakPossibleValues() -> [Int] {
if let tieBreakValueTeamOne = setDescriptor.tieBreakValueTeamOne, let tieBreakValueTeamTwo = setDescriptor.tieBreakValueTeamTwo {
if tieBreakValueTeamOne == 6 && tieBreakValueTeamTwo == 8 {
return []
}
if tieBreakValueTeamOne < 7 && tieBreakValueTeamTwo == 7 {
return [9, 5, 4, 3, 2, 1, 0]
}
if tieBreakValueTeamOne == 7 && tieBreakValueTeamTwo < 7 {
return [9, 5, 4, 3, 2, 1, 0]
}
return Array(((max(tieBreakValueTeamOne, tieBreakValueTeamTwo)+2)..<max(tieBreakValueTeamOne, tieBreakValueTeamTwo)+8)).reversed()
}
if setDescriptor.tieBreakValueTeamOne != nil {
return [9, 5, 4, 3, 2, 1, 0]
}
return SetFormat.six.possibleValues
}
var body: some View {
Section {
DisclosureGroup(isExpanded: $showSetInputView) {
PointSelectionView(valueSelected: currentValue, values: possibleValues(), possibleValues: setFormat.possibleValues, disableValues: disableValues, deleteAction: deleteLastValue)
} label: {
SetLabelView(initialValueLeft: $setDescriptor.valueTeamOne, initialValueRight: $setDescriptor.valueTeamTwo, shouldDisplaySteppers: isMainViewTieBreakView)
}
if showTieBreakView {
DisclosureGroup(isExpanded: $showTieBreakInputView) {
PointSelectionView(valueSelected: currentTiebreakValue, values: tieBreakPossibleValues(), possibleValues: SetFormat.six.possibleValues, disableValues: disableTieBreakValues, deleteAction: deleteLastTiebreakValue)
} label: {
SetLabelView(initialValueLeft: $setDescriptor.tieBreakValueTeamOne, initialValueRight: $setDescriptor.tieBreakValueTeamTwo, shouldDisplaySteppers: showTieBreakInputView, isTieBreak: true)
}
}
}
.onChange(of: setDescriptor.valueTeamOne, perform: { newValue in
if let newValue {
if newValue == setFormat.scoreToWin - 1 && setFormat.tieBreak == 8 {
setDescriptor.valueTeamTwo = setFormat.scoreToWin
} else if newValue == setFormat.scoreToWin - 2 && setFormat.tieBreak == 8 {
setDescriptor.valueTeamTwo = setFormat.scoreToWin
} else if newValue == setFormat.scoreToWin - 1 {
setDescriptor.valueTeamTwo = setFormat.scoreToWin + 1
} else if newValue <= setFormat.scoreToWin - 2 {
setDescriptor.valueTeamTwo = setFormat.scoreToWin
} else if newValue > 10 && setFormat == .superTieBreak {
setDescriptor.valueTeamTwo = newValue - 2
} else if newValue > 15 && setFormat == .megaTieBreak {
setDescriptor.valueTeamTwo = newValue - 2
}
}
})
.onChange(of: setDescriptor.valueTeamTwo, perform: { newValue in
if setDescriptor.valueTeamOne != nil && setDescriptor.valueTeamTwo != nil {
showSetInputView = false
}
})
.onChange(of: setDescriptor.tieBreakValueTeamOne, perform: { newValue in
if let newValue, setDescriptor.tieBreakValueTeamTwo == nil {
if newValue > 7 {
setDescriptor.tieBreakValueTeamTwo = newValue - 2
}
if newValue == 6 {
setDescriptor.tieBreakValueTeamTwo = newValue + 2
}
if newValue <= 5 {
setDescriptor.tieBreakValueTeamTwo = 7
}
}
else if let newValue, let tieBreakValueTeamTwo = setDescriptor.tieBreakValueTeamTwo {
if newValue > 6 && tieBreakValueTeamTwo < newValue {
setDescriptor.tieBreakValueTeamTwo = newValue - 2
}
if newValue > 6 && tieBreakValueTeamTwo > newValue {
setDescriptor.tieBreakValueTeamTwo = newValue + 2
}
if newValue == 6 {
setDescriptor.tieBreakValueTeamTwo = newValue + 2
}
if newValue <= 5 {
setDescriptor.tieBreakValueTeamTwo = 7
showTieBreakInputView = false
}
}
})
.onChange(of: setDescriptor.tieBreakValueTeamTwo, perform: { newValue in
if let tieBreakValueTeamOne = setDescriptor.tieBreakValueTeamOne, tieBreakValueTeamOne <= 5 {
showTieBreakInputView = false
} else {
if let tieBreakValueTeamTwo = setDescriptor.tieBreakValueTeamTwo {
if let newValue {
if newValue > 6 && tieBreakValueTeamTwo > setDescriptor.tieBreakValueTeamOne ?? 0 {
setDescriptor.tieBreakValueTeamOne = newValue - 2
}
if newValue > 4 && tieBreakValueTeamTwo < setDescriptor.tieBreakValueTeamOne ?? 0 {
setDescriptor.tieBreakValueTeamOne = newValue + 2
}
}
}
if let newValue, let tieBreakValueTeamOne = setDescriptor.tieBreakValueTeamOne {
if newValue < 6 && tieBreakValueTeamOne == 7 {
showTieBreakInputView = false
}
}
}
})
.listRowSeparator(.hidden)
}
}

@ -0,0 +1,57 @@
//
// SetLabelView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import SwiftUI
struct SetLabelView: View {
@Binding var initialValueLeft: Int?
@Binding var initialValueRight: Int?
@State private var valueLeft: Int = 0
@State private var valueRight: Int = 0
var shouldDisplaySteppers: Bool = false
var isTieBreak: Bool = false
var body: some View {
HStack(spacing: 0) {
if shouldDisplaySteppers {
Stepper(value: $valueLeft, in: 0...Int.max) {
} onEditingChanged: { didChange in
initialValueLeft = valueLeft
}
.fixedSize()
.scaleEffect(0.7)
}
Spacer()
Text("\(valueLeft) / \(valueRight)")
.font(isTieBreak ? .headline : .largeTitle).monospacedDigit()
.scaledToFit()
.minimumScaleFactor(0.5)
.lineLimit(1)
Spacer()
if shouldDisplaySteppers {
Stepper(value: $valueRight, in: 0...Int.max) {
} onEditingChanged: { didChange in
initialValueRight = valueRight
}
.fixedSize()
.scaleEffect(0.7)
}
}
.onChange(of: initialValueLeft) { newValue in
valueLeft = initialValueLeft ?? 0
}
.onChange(of: initialValueRight) { newValue in
valueRight = initialValueRight ?? 0
}
.onAppear {
valueLeft = initialValueLeft ?? 0
valueRight = initialValueRight ?? 0
}
}
}

@ -0,0 +1,23 @@
//
// MatchTypeSmallSelectionView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import SwiftUI
struct MatchTypeSmallSelectionView: View {
@Binding var selectedFormat: MatchFormat
let format: String
var body: some View {
Picker(selection: $selectedFormat) {
ForEach(MatchFormat.allCases, id: \.rawValue) { matchFormat in
Text(format + " " + matchFormat.format)
.tag(matchFormat)
}
} label: {
}
}
}

@ -24,7 +24,7 @@ struct TeamPickerView: View {
List { List {
let teams = tournament.sortedTeams() let teams = tournament.sortedTeams()
Section { Section {
_teamListView(teams.filter({ $0.available() }).sorted(by: \.weight).reversed()) _teamListView(teams.filter({ $0.availableForSeedPick() }).sorted(by: \.weight).reversed())
} header: { } header: {
Text("Disponible") Text("Disponible")
} }

@ -32,7 +32,7 @@ struct UpdateSourceRankDateView: View {
} }
} }
RowButtonView(title: "Valider") { RowButtonView("Valider") {
updatingRank = true updatingRank = true
Task { Task {
do { do {

@ -208,12 +208,12 @@ struct InscriptionManagerView: View {
} description: { } description: {
Text("\(searchField) est introuvable dans les équipes inscrites.") Text("\(searchField) est introuvable dans les équipes inscrites.")
} actions: { } actions: {
RowButtonView(title: "Modifier la recherche") { RowButtonView("Modifier la recherche") {
searchField = "" searchField = ""
presentSearch = true presentSearch = true
} }
RowButtonView(title: "Créer une équipe") { RowButtonView("Créer une équipe") {
Task { Task {
await MainActor.run() { await MainActor.run() {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
@ -222,7 +222,7 @@ struct InscriptionManagerView: View {
} }
} }
RowButtonView(title: "D'accord") { RowButtonView("D'accord") {
searchField = "" searchField = ""
presentSearch = false presentSearch = false
} }
@ -564,16 +564,16 @@ struct InscriptionManagerView: View {
if editedTeam == nil { if editedTeam == nil {
if createdPlayerIds.isEmpty { if createdPlayerIds.isEmpty {
RowButtonView(title: "Bloquer une place") { RowButtonView("Bloquer une place") {
_createTeam() _createTeam()
} }
} else { } else {
RowButtonView(title: "Ajouter l'équipe") { RowButtonView("Ajouter l'équipe") {
_createTeam() _createTeam()
} }
} }
} else { } else {
RowButtonView(title: "Modifier l'équipe") { RowButtonView("Modifier l'équipe") {
_updateTeam() _updateTeam()
} }
} }
@ -601,11 +601,11 @@ struct InscriptionManagerView: View {
} description: { } description: {
Text("Aucun joueur classé n'a été trouvé dans ce message.") Text("Aucun joueur classé n'a été trouvé dans ce message.")
} actions: { } actions: {
RowButtonView(title: "Créer un joueur non classé") { RowButtonView("Créer un joueur non classé") {
presentPlayerCreation = true presentPlayerCreation = true
} }
RowButtonView(title: "Effacer cette recherche") { RowButtonView("Effacer cette recherche") {
self.pasteString = nil self.pasteString = nil
} }
} }

@ -49,7 +49,7 @@ struct TournamentView: View {
} }
if endOfInscriptionDate < Date() { if endOfInscriptionDate < Date() {
RowButtonView(title: "Clôturer les inscriptions") { RowButtonView("Clôturer les inscriptions") {
tournament.lockRegistration() tournament.lockRegistration()
_save() _save()
} }

@ -0,0 +1,26 @@
//
// TabItemModifier.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/04/2024.
//
import SwiftUI
struct TabItemModifier: ViewModifier {
let tabDestination: TabDestination
func body(content: Content) -> some View {
content
.tabItem {
Label(tabDestination.title, systemImage: tabDestination.image)
}
.tag(tabDestination as TabDestination?)
}
}
extension View {
func tabItem(for tabDestination: TabDestination) -> some View {
modifier(TabItemModifier(tabDestination: tabDestination))
}
}
Loading…
Cancel
Save