multistore
Razmig Sarkissian 2 years ago
parent 55b69bc9b4
commit 992d7fb744
  1. 79
      PadelClub.xcodeproj/project.pbxproj
  2. 171
      PadelClub/Data/GroupStage.swift
  3. 146
      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. 36
      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. 38
      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. 553
      PadelClub/Views/GroupStage/GroupStageView.swift
  24. 73
      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. 33
      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 */; };
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.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 */; };
FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.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 */; };
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.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 */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; };
FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.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 */; };
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.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>"; };
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>"; };
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>"; };
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>"; };
@ -401,12 +414,22 @@
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -426,6 +449,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */,
FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -562,6 +586,7 @@
FFC83D4B2BB807C200750834 /* Round */,
FF967CF92BAEE11500A9A3BD /* GroupStage */,
FF967CFE2BAEEF5A00A9A3BD /* Match */,
FFCFC00B2BBC39A600B82851 /* Score */,
FF967D072BAF3D3000A9A3BD /* Team */,
FF089EB92BB011EE00F0AEC7 /* Player */,
FF3F74F72B919F96004CFE0E /* Tournament */,
@ -613,6 +638,7 @@
C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */,
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */,
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */,
FFBF065D2BBD8040009D6715 /* MatchListView.swift */,
FF967CF72BAEDF0000A9A3BD /* Labels.swift */,
);
path = Components;
@ -784,6 +810,8 @@
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */,
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */,
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */,
FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */,
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -796,6 +824,7 @@
FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */,
FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */,
FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */,
FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */,
);
path = Shared;
sourceTree = "<group>";
@ -859,6 +888,7 @@
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */,
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */,
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */,
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */,
);
path = GroupStage;
sourceTree = "<group>";
@ -896,6 +926,18 @@
path = Round;
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 */ = {
isa = PBXGroup;
children = (
@ -913,6 +955,7 @@
children = (
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */,
FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */,
FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */,
);
path = ViewModifiers;
sourceTree = "<group>";
@ -967,6 +1010,9 @@
dependencies = (
);
name = PadelClub;
packageProductDependencies = (
FFCFBFFD2BBBE86600B82851 /* Algorithms */,
);
productName = PadelClub;
productReference = C425D3FD2B6D249D002A7B48 /* PadelClub.app */;
productType = "com.apple.product-type.application";
@ -1039,6 +1085,9 @@
Base,
);
mainGroup = C425D3F42B6D249D002A7B48;
packageReferences = (
FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
);
productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */;
projectDirPath = "";
projectReferences = (
@ -1160,6 +1209,7 @@
FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */,
FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */,
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */,
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */,
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */,
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */,
C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */,
@ -1182,7 +1232,9 @@
FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */,
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */,
FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */,
FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */,
FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */,
FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */,
FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */,
@ -1201,6 +1253,7 @@
FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */,
FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */,
C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */,
FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */,
FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */,
FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */,
C4A47D7D2B73CDC300ADC637 /* ClubV1.swift in Sources */,
@ -1212,6 +1265,7 @@
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */,
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */,
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */,
@ -1230,15 +1284,19 @@
FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */,
FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */,
FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */,
FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */,
FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */,
FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */,
FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */,
C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */,
FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */,
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */,
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */,
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */,
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
@ -1263,11 +1321,13 @@
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */,
C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */,
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */,
FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */,
FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */,
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */,
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
@ -1611,6 +1671,25 @@
};
/* 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 */
FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = {
isa = XCVersionGroup;

@ -7,6 +7,7 @@
import Foundation
import LeStorage
import Algorithms
@Observable
class GroupStage: ModelObject, Storable {
@ -36,8 +37,8 @@ class GroupStage: ModelObject, Storable {
self.startDate = startDate
}
func teamsAt(_ index: Int) -> TeamRegistration? {
teams().first(where: { $0.groupStagePosition == index })
func teamAt(groupStagePosition: Int) -> TeamRegistration? {
teams().first(where: { $0.groupStagePosition == groupStagePosition })
}
func tournamentObject() -> Tournament? {
@ -58,23 +59,25 @@ class GroupStage: ModelObject, Storable {
}
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
matches.filter { $0.hasEnded() }.isEmpty == false
_matches().filter { $0.hasEnded() }.isEmpty == false
}
func hasEnded() -> Bool {
if matches.isEmpty { return false }
return matches.allSatisfy { $0.hasEnded() }
guard teams().count == size else { return false }
let _matches = _matches()
if _matches.isEmpty { return false }
return _matches.allSatisfy { $0.hasEnded() }
}
func buildMatches() {
removeMatches()
_removeMatches()
var _matches = [Match]()
for i in 0..<numberOfMatchesToBuild {
for i in 0..<_numberOfMatchesToBuild() {
let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat)
_matches.append(newMatch)
}
@ -82,24 +85,162 @@ class GroupStage: ModelObject, Storable {
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()
}
var numberOfMatchesToBuild: Int {
private func _numberOfMatchesToBuild() -> Int {
(size * (size - 1)) / 2
}
var matches: [Match] {
private func _matches() -> [Match] {
Store.main.filter { $0.groupStage == self.id }
}
func teams() -> [TeamRegistration] {
Store.main.filter { $0.groupStage == self.id }
fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool
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 {
try Store.main.deleteDependencies(items: self.matches)
try Store.main.deleteDependencies(items: self._matches())
}
}
@ -120,6 +261,6 @@ extension GroupStage: Selectable {
}
func badgeValue() -> Int? {
nil
runningMatches().count
}
}

@ -54,6 +54,10 @@ class Match: ModelObject, Storable {
}
func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if let groupStageObject {
return groupStageObject.localizedMatchUpLabel(for: index)
}
switch displayStyle {
case .wide:
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 {
teams().count == 2
}
@ -133,7 +214,7 @@ class Match: ModelObject, Storable {
}
func hasEnded() -> Bool {
endDate != nil
endDate != nil || hasWalkoutTeam() || winningTeamId != nil
}
func isGroupStage() -> Bool {
@ -166,26 +247,33 @@ class Match: ModelObject, Storable {
func teams() -> [TeamRegistration] {
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 }
}
func scoreDifference(_ whichTeam: Int) -> (set: Int, game: Int)? {
guard let teamScoreTeam = teamScore(.one), let teamScoreOtherTeam = teamScore(.two) else { return nil }
var reverseValue = 1
if whichTeam == team(.two)?.groupStagePosition {
reverseValue = -1
}
let endedSetsOne = teamScoreTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreTeam.isWalkOut())
let endedSetsTwo = teamScoreOtherTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreOtherTeam.isWalkOut())
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)
}
func groupStageProjectedTeam(_ team: TeamData) -> TeamRegistration? {
guard groupStage != nil else { return nil }
switch team {
case .one:
if let teamId = topPreviousRoundMatch()?.winningTeamId {
return Store.main.findById(teamId)
}
case .two:
if let teamId = bottomPreviousRoundMatch()?.winningTeamId {
return Store.main.findById(teamId)
}
}
return nil
guard let groupStageObject else { return nil }
return groupStageObject.team(whichTeam: team, inMatchIndex: index)
}
func seed(_ team: TeamData) -> TeamRegistration? {
@ -219,16 +307,17 @@ class Match: ModelObject, Storable {
}
func teamWon(_ team: TeamData) -> Bool {
true
guard let winningTeamId else { return false }
return winningTeamId == self.team(team)?.id
}
func team(_ team: TeamData) -> TeamRegistration? {
if groupStage != nil {
switch team {
case .one:
return teams().first
return groupStageProjectedTeam(.one)
case .two:
return teams().last
return groupStageProjectedTeam(.two)
}
} else {
switch team {
@ -245,7 +334,7 @@ class Match: ModelObject, Storable {
}
func teamWalkOut(_ team: TeamData) -> Bool {
false
teamScore(team)?.isWalkOut() == true
}
func teamScore(_ team: TeamData) -> TeamScore? {
@ -313,3 +402,20 @@ class Match: ModelObject, Storable {
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 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) {
self.teamRegistration = teamRegistration
self.firstName = firstName
@ -251,6 +253,7 @@ class PlayerRegistration: ModelObject, Storable {
case _email = "email"
case _weight = "weight"
case _source = "source"
case _hasArrived = "hasArrived"
}

@ -18,7 +18,7 @@ class TeamRegistration: ModelObject, Storable {
var registrationDate: Date?
var callDate: Date?
var bracketPosition: Int?
var groupStagePosition: Int?
var groupStagePosition: Int? //todo devrait être non nil ?
var comment: String?
var source: String?
var sourceValue: String?
@ -32,6 +32,7 @@ class TeamRegistration: ModelObject, Storable {
var weight: Int = 0
var lockWeight: Int?
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) {
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
}
@ -203,7 +208,7 @@ class TeamRegistration: ModelObject, Storable {
}
}
func qualified() -> Bool {
func qualifiedFromGroupStage() -> Bool {
groupStagePosition != nil && bracketPosition != nil
}
@ -304,6 +309,7 @@ class TeamRegistration: ModelObject, Storable {
case _walkOut = "walkOut"
case _lockWeight = "lockWeight"
case _confirmationDate = "confirmationDate"
case _qualified = "qualified"
}
}

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

@ -461,11 +461,11 @@ class Tournament : ModelObject, Storable {
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 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 {
@ -505,7 +505,7 @@ class Tournament : ModelObject, Storable {
func qualifiedTeams() -> [TeamRegistration] {
unsortedTeams().filter({ $0.qualified() })
unsortedTeams().filter({ $0.qualifiedFromGroupStage() })
}
func moreQualifiedToDraw() -> Int {
@ -517,7 +517,7 @@ class Tournament : ModelObject, Storable {
return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in
groupStage.teams()[qualifiedPerGroupStage]
}
.filter({ $0.qualified() == false })
.filter({ $0.qualifiedFromGroupStage() == false })
} else {
return []
}
@ -546,11 +546,11 @@ class Tournament : ModelObject, Storable {
let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == 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
} 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
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 {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { a, b in
@ -23,16 +30,18 @@ extension Sequence {
extension Sequence {
func concurrentForEach(
_ operation: @escaping (Element) async -> Void
) async {
_ operation: @escaping (Element) async throws -> Void
) async throws {
// A task group automatically waits for all of its
// sub-tasks to complete, while also performing those
// tasks in parallel:
await withTaskGroup(of: Void.self) { group in
try await withThrowingTaskGroup(of: Void.self) { group in
for element in self {
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 {
do {
@ -128,24 +128,28 @@ recherche_type=club&club[autocomplete][value_container][value_field]=\(codeClub.
request.httpMethod = "POST"
request.httpBody = postData
do {
let commands : [HttpCommand] = try await runTenupTask(request: request)
let resultCommand = commands.first(where: { $0.results != nil })
if let gatheredTournaments = resultCommand?.results?.items {
var finalTournaments = tournaments + gatheredTournaments
if let count = resultCommand?.results?.nb_results {
if finalTournaments.count < count {
let newTournaments = await getClubFederalTournaments(page: page+1, tournaments: finalTournaments, club: club, codeClub: codeClub)
finalTournaments = finalTournaments + newTournaments
}
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 })
if let gatheredTournaments = resultCommand?.results?.items {
var finalTournaments = tournaments + gatheredTournaments
if let count = resultCommand?.results?.nb_results {
if finalTournaments.count < count {
let newTournaments = try await getClubFederalTournaments(page: page+1, tournaments: finalTournaments, club: club, codeClub: codeClub)
finalTournaments = finalTournaments + newTournaments
}
return finalTournaments
}
} catch {
print("getClubFederalTournaments", error)
return finalTournaments
}
// do {
// } catch {
// print("getClubFederalTournaments", error)
// }
//
return []
}

@ -14,4 +14,13 @@ enum NetworkManagerError: LocalizedError {
case mailNotSent //no network no error
case messageFailed
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)
}
}
}

@ -971,6 +971,10 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func defaultWalkOutScore(_ asWalkOutTeam: Bool) -> [Int] {
Array(repeating: asWalkOutTeam ? 0 : setFormat.scoreToWin, count: setsToWin)
}
var weight: Int {
switch self {

@ -78,7 +78,7 @@ class SourceFileManager {
allFiles.contains(where: { $0.dateFromPath == date }) == false
}
await dates.concurrentForEach { date in
try? await dates.concurrentForEach { date in
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: {
Text("Une erreur est survenue lors de la récupération de votre localisation.")
} actions: {
RowButtonView(title: "D'accord") {
RowButtonView("D'accord") {
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.")
} actions: {
if locationManager.manager.authorizationStatus != .restricted {
RowButtonView(title: "Chercher autour de moi") {
RowButtonView("Chercher autour de moi") {
if locationManager.manager.authorizationStatus == .notDetermined {
locationManager.manager.requestWhenInUseAuthorization()
} 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
}
}

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

@ -26,7 +26,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.background {
Circle()
.fill(Color.white)
.opacity(selectedDestination == nil ? 1.0 : 0.5)
.opacity(selectedDestination == nil ? 1.0 : 0.4)
}
.buttonStyle(.plain)
}
@ -41,7 +41,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.background {
Capsule()
.fill(Color.white)
.opacity(selectedDestination?.id == destination.id ? 1.0 : 0.5)
.opacity(selectedDestination?.id == destination.id ? 1.0 : 0.4)
}
.buttonStyle(.plain)
.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
fileprivate let defaultConfirmationMessage = "Êtes-vous sûr de vouloir faire cela ?"
struct RowButtonView: View {
var role: ButtonRole? = nil
let title: String
var systemImage: String? = nil
var image: String? = nil
var animatedProgress: Bool = false
let confirmationMessage: String
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 {
Button {
action()
Button(role: role) {
if role == .destructive {
askConfirmation = true
} else {
action()
}
} label: {
HStack {
if animatedProgress {
@ -47,8 +66,19 @@ struct RowButtonView: View {
.disabled(animatedProgress)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.tint(.launchScreenBackground)
.tint(role == .destructive ? Color.red : Color.launchScreenBackground)
.listRowBackground(Color.clear)
.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 {
RowButtonView(title:"Valider") {
RowButtonView("Valider") {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil {
let event = Event(name: eventName)
event.club = selectedClub?.id
@ -143,7 +143,7 @@ struct EventCreationView: View {
}
Section {
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
RowButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}

@ -38,7 +38,7 @@ struct GroupStageSettingsView: View {
// if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) {
// Section {
// RowButtonView(title: "Reconstruire les poules") {
// RowButtonView("Reconstruire les poules") {
// confirmGroupStageRebuild = true
// }
// .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) {
// Section {
// RowButtonView(title: "Générer les matchs de poules") {
// RowButtonView("Générer les matchs de poules") {
// startAllGroupStageConfirmation = true
// }
// .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,209 +8,250 @@
import SwiftUI
struct GroupStageView: View {
@EnvironmentObject var dataStore: DataStore
@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 sortingMode: GroupStageSortingMode = .auto
@State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false
private enum GroupStageSortingMode {
case auto
case score
case weight
}
enum MenuLink: Int, Identifiable, Hashable {
var id: Int { self.rawValue }
case prepare
var sortByScore: Bool {
sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score
}
var groupStageView: some View {
ForEach(0..<(groupStage.size), id: \.self) { index in
// let entrant : Entrant? = runningGroupStageOrderedByScore ? groupStage.orderedByScore[Int(index)] : groupStage.entrantAtIndex(Int(index))
if let team = groupStage.teamsAt(index) {
Text(team.teamLabel())
// GroupStageEntrantMenuView(entrant: entrant, groupStage: groupStage, index: index.intValue)
} else {
Menu {
// 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: {
HStack {
Text("#\(index+1)")
Text("Aucune équipe")
}
}
}
}
func teamAt(atIndex index: Int) -> TeamRegistration? {
sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index)
}
var body: some View {
List {
Section {
groupStageView
// .disabled(canUpdateTournament == false)
// .sheet(item: $selectedMenuLink) { selectedMenuLink in
// switch selectedMenuLink {
// case .prepare:
// PrepareGroupStageView(groupStage: groupStage)
// }
// }
_groupStageView()
} header: {
HStack {
if groupStage.isBroadcasted() {
Label(groupStage.groupStageTitle(), systemImage: "airplayvideo")
} else {
Text(groupStage.groupStageTitle())
}
Spacer()
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 {
sortingMode = .weight
}
} label: {
Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly)
}
}
} footer: {
HStack {
if groupStage.matches.isEmpty {
Button {
//groupStage.startGroupStage()
//save()
} label: {
Text("Créer les matchs")
.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()
}
}
}
private func _groupStageView() -> some View {
ForEach(0..<(groupStage.size), id: \.self) { index in
if let team = teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition {
NavigationLink {
GroupStageTeamView(groupStage: groupStage, team: team)
} label: {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 0) {
HStack {
Text("#\(groupStagePosition + 1)")
Text("Poids \(team.weight)")
}
.font(.caption)
HStack {
if let teamName = team.name {
Text(teamName)
} else {
VStack(alignment: .leading) {
ForEach(team.players()) { player in
Text(player.playerLabel())
}
}
}
if team.qualified {
Image(systemName: "checkmark.seal")
}
}
}
Spacer()
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) {
Text(score)
}
.buttonStyle(.borderless)
}
Spacer()
Menu {
// Button {
// selectedMenuLink = .prepare
// } label: {
// Label("Préparer", systemImage: "calendar")
// }
//
// Menu {
// MenuWarnView(warningSender: groupStage)
// } label: {
// Label("Prévenir", systemImage: "person.crop.circle")
// }
//
// if groupStage.isBroadcasted() {
// Button {
// groupStage.refreshBroadcast()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcast()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } else if groupStage.tournament?.canBroadcast() == true {
// Button {
// Task {
// try? await groupStage.broadcastGroupStage()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
// }
//
// Divider()
// if groupStage.tournament?.canBroadcast() == true {
// Menu {
// Button {
// Task {
// try? await groupStage.broadcastGroupStageMatches()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
//
// Button {
// groupStage.refreshBroadcastMatches()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcastMatches()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } label: {
// Text("Diffusion des matchs")
// }
// }
//
// Divider()
// Menu {
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.startGroupStage()
// save()
// } label: {
// Label("Re-démarrer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.removeMatches()
// save()
// } label: {
// Label("Supprimer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// Button(role: .destructive) {
// groupStage.tournament?.completeEntries.filter { $0.groupStagePosition == groupStage.index }.forEach { $0.resetGroupStagePosition() }
// groupStage.tournament?.removeFromGroupStages(groupStage)
// groupStage.tournament?.numberOfGroupStages -= 1
// save()
// } label: {
// Label("Supprimer la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// } label: {
// Text("Éditer")
// }
} label: {
}
} else {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 0) {
HStack {
Spacer()
Label("Options", systemImage: "ellipsis.circle").labelStyle(.titleOnly)
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)
})
}
.buttonStyle(.borderless)
}
}
}
}
private func _groupStageMenuView() -> some View {
Menu {
if groupStage.matches().isEmpty {
Button {
//groupStage.startGroupStage()
//save()
} label: {
Text("Créer les matchs")
}
.buttonStyle(.borderless)
}
if groupStage.matches.isEmpty == false {
Section {
ForEach(groupStage.matches) { match in
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
}
} header: {
Text("Matchs de la " + groupStage.groupStageTitle())
Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true
}
Button("Recommencer tous les matchs", role: .destructive) {
confirmResetMatch = true
}
// Button {
// selectedMenuLink = .prepare
// } label: {
// Label("Préparer", systemImage: "calendar")
// }
//
// Menu {
// MenuWarnView(warningSender: groupStage)
// } label: {
// Label("Prévenir", systemImage: "person.crop.circle")
// }
//
// if groupStage.isBroadcasted() {
// Button {
// groupStage.refreshBroadcast()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcast()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } else if groupStage.tournament?.canBroadcast() == true {
// Button {
// Task {
// try? await groupStage.broadcastGroupStage()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
// }
//
// Divider()
// if groupStage.tournament?.canBroadcast() == true {
// Menu {
// Button {
// Task {
// try? await groupStage.broadcastGroupStageMatches()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
//
// Button {
// groupStage.refreshBroadcastMatches()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcastMatches()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } label: {
// Text("Diffusion des matchs")
// }
// }
//
// Divider()
// Menu {
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.startGroupStage()
// save()
// } label: {
// Label("Re-démarrer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.removeMatches()
// save()
// } label: {
// Label("Supprimer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// Button(role: .destructive) {
// groupStage.tournament?.completeEntries.filter { $0.groupStagePosition == groupStage.index }.forEach { $0.resetGroupStagePosition() }
// groupStage.tournament?.removeFromGroupStages(groupStage)
// groupStage.tournament?.numberOfGroupStages -= 1
// save()
// } label: {
// Label("Supprimer la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// } label: {
// Text("Éditer")
// }
} label: {
LabelOptions()
}
.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)
}
}
// .onAppear {
// if let tournament = groupStage.tournament {
// canUpdateTournament = PersistenceController.shared.container.canUpdateRecord(forManagedObjectWith: tournament.objectID)
// } else {
// canUpdateTournament = true
// }
// }
.confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) {
Button("Oui") {
groupStage.buildMatches()
}
}
}
// func save() {
@ -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 {
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) {
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 {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedGroupStage, destinations: tournament.groupStages(), nilDestinationIsValid: true)
switch selectedGroupStage {
case .none:
GroupStageSettingsView()
.navigationTitle("Réglages")
case .some(let groupStage):
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination {
case .all:
List {
let allGroupStages = tournament.groupStages()
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)
.navigationTitle(groupStage.groupStageTitle())
case nil:
GroupStageSettingsView()
.navigationTitle("Réglages")
}
}
.navigationBarTitleDisplayMode(.inline)

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

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

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

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

@ -16,13 +16,13 @@ struct EmptyActivityView: View {
WelcomeView()
Section {
RowButtonView(title: "Créer votre premier événement", action: {
RowButtonView("Créer votre premier événement", action: {
newTournament = Tournament.newEmptyInstance()
})
}
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 {
MainView()
}

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

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

@ -23,14 +23,14 @@ struct RoundSettingsView: View {
Toggle("Éditer les têtes de série", isOn: $isEditingTournamentSeed)
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 })
}
}
Section {
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)
}
}
@ -38,7 +38,7 @@ struct RoundSettingsView: View {
Section {
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 matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
@ -64,7 +64,7 @@ struct RoundSettingsView: View {
if let roundIndex {
RowButtonView(title: "Valider") {
RowButtonView("Valider") {
if availableSeedGroup == SeedInterval(first: 1, last: 2) {
let seeds = tournament.seeds()
// 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 {
let teams = tournament.sortedTeams()
Section {
_teamListView(teams.filter({ $0.available() }).sorted(by: \.weight).reversed())
_teamListView(teams.filter({ $0.availableForSeedPick() }).sorted(by: \.weight).reversed())
} header: {
Text("Disponible")
}

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

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

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