clean up tenup

multistore
Razmig Sarkissian 2 years ago
parent fd661687c3
commit c0fb636621
  1. 34
      PadelClub.xcodeproj/project.pbxproj
  2. 7
      PadelClub/Data/DataStore.swift
  3. 4
      PadelClub/Data/Event.swift
  4. 28
      PadelClub/Data/Federal/ClubHolder.swift
  5. 585
      PadelClub/Data/Federal/FederalTournament.swift
  6. 30
      PadelClub/Data/Federal/FederalTournamentHolder.swift
  7. 77
      PadelClub/Data/Tournament.swift
  8. 24
      PadelClub/Data/User.swift
  9. 26
      PadelClub/Extensions/Calendar+Extensions.swift
  10. 139
      PadelClub/Extensions/Date+Extensions.swift
  11. 8
      PadelClub/Extensions/String+Extensions.swift
  12. 110
      PadelClub/Manager/Network/NetworkFederalService.swift
  13. 15
      PadelClub/Manager/PadelRule.swift
  14. 1
      PadelClub/PadelClubApp.swift
  15. 10
      PadelClub/ViewModel/AgendaDestination.swift
  16. 14
      PadelClub/ViewModel/FederalTournamentSearchScope.swift
  17. 2
      PadelClub/ViewModel/TabDestination.swift
  18. 15
      PadelClub/Views/Club/ClubsView.swift
  19. 191
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  20. 120
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  21. 94
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  22. 59
      PadelClub/Views/Navigation/Agenda/TenupEventListView.swift
  23. 21
      PadelClub/Views/Navigation/MainView.swift
  24. 2
      PadelClub/Views/Navigation/Organizer/TournamentButtonView.swift
  25. 5
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  26. 88
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  27. 4
      PadelClub/Views/Shared/SelectablePlayerListView.swift
  28. 3
      PadelClub/Views/Tournament/Shared/DateBoxView.swift
  29. 35
      PadelClub/Views/Tournament/Shared/PadelClubButtonView.swift
  30. 83
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  31. 4
      PadelClub/Views/User/LoginView.swift

@ -81,6 +81,12 @@
FF0EC5752BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5342BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv */; }; FF0EC5752BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5342BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv */; };
FF0EC5762BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5452BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv */; }; FF0EC5762BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5452BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv */; };
FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; }; FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; };
FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; };
FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; };
FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; };
FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */; };
FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC202BB53E590036DAAB /* ClubHolder.swift */; };
FF1CBC252BB54C9F0036DAAB /* TenupEventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC242BB54C9F0036DAAB /* TenupEventListView.swift */; };
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; }; FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; };
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; };
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; }; FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; };
@ -107,6 +113,7 @@
FF5D0D852BB48997005CB568 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; }; FF5D0D852BB48997005CB568 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; };
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; }; FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; };
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; }; FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; };
FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; };
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; };
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; };
FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; };
@ -155,6 +162,7 @@
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; };
FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; };
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; };
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; };
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; };
@ -299,6 +307,12 @@
FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-10-2022.csv"; sourceTree = "<group>"; }; FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-10-2022.csv"; sourceTree = "<group>"; };
FF0EC54B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-11-2022.csv"; sourceTree = "<group>"; }; FF0EC54B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-11-2022.csv"; sourceTree = "<group>"; };
FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = "<group>"; }; FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = "<group>"; };
FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = "<group>"; };
FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = "<group>"; };
FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = "<group>"; };
FF1CBC202BB53E590036DAAB /* ClubHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubHolder.swift; sourceTree = "<group>"; };
FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentHolder.swift; sourceTree = "<group>"; };
FF1CBC242BB54C9F0036DAAB /* TenupEventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenupEventListView.swift; sourceTree = "<group>"; };
FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubDetailView.swift; sourceTree = "<group>"; }; FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubDetailView.swift; sourceTree = "<group>"; };
FF1DC5522BAB354A00FD8220 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; }; FF1DC5522BAB354A00FD8220 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateClubView.swift; sourceTree = "<group>"; }; FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateClubView.swift; sourceTree = "<group>"; };
@ -323,6 +337,7 @@
FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankCalculatorView.swift; sourceTree = "<group>"; }; FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankCalculatorView.swift; sourceTree = "<group>"; };
FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+Extensions.swift"; sourceTree = "<group>"; }; FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+Extensions.swift"; sourceTree = "<group>"; };
FF5D0D882BB4935C005CB568 /* ClubRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubRowView.swift; sourceTree = "<group>"; }; FF5D0D882BB4935C005CB568 /* ClubRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubRowView.swift; sourceTree = "<group>"; };
FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; };
FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; };
FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; }; FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
@ -371,6 +386,7 @@
FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPickerView.swift; sourceTree = "<group>"; }; FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPickerView.swift; sourceTree = "<group>"; };
FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDateView.swift; sourceTree = "<group>"; }; FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDateView.swift; sourceTree = "<group>"; };
FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBlockView.swift; sourceTree = "<group>"; }; FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBlockView.swift; sourceTree = "<group>"; };
FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubButtonView.swift; sourceTree = "<group>"; };
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; }; FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; }; FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@ -505,6 +521,7 @@
children = ( children = (
C4A47D752B73787D00ADC637 /* Migration */, C4A47D752B73787D00ADC637 /* Migration */,
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */, C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */,
C4A47DAC2B85FCCD00ADC637 /* User.swift */,
C4A47D592B6D383C00ADC637 /* Tournament.swift */, C4A47D592B6D383C00ADC637 /* Tournament.swift */,
FF967CE72BAEC70100A9A3BD /* GroupStage.swift */, FF967CE72BAEC70100A9A3BD /* GroupStage.swift */,
FF967CED2BAECBD700A9A3BD /* Round.swift */, FF967CED2BAECBD700A9A3BD /* Round.swift */,
@ -562,7 +579,6 @@
C4A47DA52B83948E00ADC637 /* LoginView.swift */, C4A47DA52B83948E00ADC637 /* LoginView.swift */,
C4A47DB02B86375E00ADC637 /* MainUserView.swift */, C4A47DB02B86375E00ADC637 /* MainUserView.swift */,
C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */, C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */,
C4A47DAC2B85FCCD00ADC637 /* User.swift */,
); );
path = User; path = User;
sourceTree = "<group>"; sourceTree = "<group>";
@ -699,6 +715,7 @@
children = ( children = (
FF7091672B90F79F00AB08DA /* TournamentCellView.swift */, FF7091672B90F79F00AB08DA /* TournamentCellView.swift */,
FF7091692B90F95E00AB08DA /* DateBoxView.swift */, FF7091692B90F95E00AB08DA /* DateBoxView.swift */,
FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -749,6 +766,7 @@
FF7091652B90F0B000AB08DA /* TabDestination.swift */, FF7091652B90F0B000AB08DA /* TabDestination.swift */,
FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */, FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */,
FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */, FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */,
FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */,
); );
path = ViewModel; path = ViewModel;
sourceTree = "<group>"; sourceTree = "<group>";
@ -778,6 +796,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */, FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */,
FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */,
FF1CBC202BB53E590036DAAB /* ClubHolder.swift */,
FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */,
); );
path = Federal; path = Federal;
sourceTree = "<group>"; sourceTree = "<group>";
@ -854,6 +875,8 @@
FFD784032B91C280000F62A6 /* EmptyActivityView.swift */, FFD784032B91C280000F62A6 /* EmptyActivityView.swift */,
FFD784012B91C1B4000F62A6 /* WelcomeView.swift */, FFD784012B91C1B4000F62A6 /* WelcomeView.swift */,
FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */, FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */,
FF1CBC242BB54C9F0036DAAB /* TenupEventListView.swift */,
FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */,
FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */, FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */,
); );
path = Agenda; path = Agenda;
@ -891,6 +914,7 @@
FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */, FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */,
FFF8ACD82B923F3C008466FA /* String+Extensions.swift */, FFF8ACD82B923F3C008466FA /* String+Extensions.swift */,
FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */, FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */,
FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */,
FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */, FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */,
FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */, FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */,
FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */, FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */,
@ -1101,6 +1125,7 @@
FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */,
FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */, FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */,
C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */,
FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */,
FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */,
C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */,
FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */, FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */,
@ -1112,6 +1137,7 @@
FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */, FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */,
FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */,
C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */,
FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */,
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */, FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */,
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */, FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */,
@ -1122,6 +1148,7 @@
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */,
FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */,
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */,
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */,
FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */,
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
@ -1142,6 +1169,7 @@
FF3795662B9399AA004EA093 /* Persistence.swift in Sources */, FF3795662B9399AA004EA093 /* Persistence.swift in Sources */,
FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */, FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */,
FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */, FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */,
FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */,
C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */, C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */,
FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */,
FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */, FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */,
@ -1161,6 +1189,7 @@
FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */,
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */, FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */,
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */, FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */,
FF1CBC252BB54C9F0036DAAB /* TenupEventListView.swift in Sources */,
FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */, FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */,
FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */, FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */,
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */,
@ -1168,6 +1197,8 @@
FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */,
FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */, FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */,
FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */, FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */,
FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */,
FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */,
FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */, FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
@ -1200,6 +1231,7 @@
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */,
C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */,
FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */,
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,

@ -37,7 +37,8 @@ class DataStore: ObservableObject {
init() { init() {
let store = Store.main let store = Store.main
store.synchronizationApiURL = "http://127.0.0.1:8000/api/" // store.synchronizationApiURL = "http://127.0.0.1:8000/api/"
store.synchronizationApiURL = "https://xlr.alwaysdata.net/api/"
// store.addMigration(Migration<ClubV1, Club>(version: 2)) // store.addMigration(Migration<ClubV1, Club>(version: 2))
// store.addMigration(Migration<TournamentV1, TournamentV2>(version: 2)) // store.addMigration(Migration<TournamentV1, TournamentV2>(version: 2))
@ -65,8 +66,8 @@ class DataStore: ObservableObject {
if let _ = Guard.main.currentPlan { if let _ = Guard.main.currentPlan {
return .creation return .creation
} }
if let user = self.user, user.club_id != nil { if let user = self.user, user.club != nil {
if user.umpire_code != nil { if user.umpireCode != nil {
return .creation return .creation
} else { } else {
return .edition return .edition

@ -42,6 +42,10 @@ class Event: ModelObject, Storable {
Store.main.filter { $0.event == self.id } Store.main.filter { $0.event == self.id }
} }
func existingBuild(_ build: any TournamentBuildHolder) -> Tournament? {
tournaments.first(where: { $0.isSameBuild(build) })
}
override func deleteDependencies() throws { override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.tournaments) try Store.main.deleteDependencies(items: self.tournaments)
} }

@ -0,0 +1,28 @@
//
// ClubHolder.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 13/11/2023.
//
import Foundation
protocol ClubHolder {
var clubName: String? { get }
}
extension ClubHolder {
var shortSmartClubName: String? {
var clubName = clubName
clubName = clubName?.replacingOccurrences(of: "TENNIS SPORTING CLUB", with: "TSC")
clubName = clubName?.replacingOccurrences(of: "SPORTING CLUB TENNIS", with: "SCT")
clubName = clubName?.replacingOccurrences(of: "TENNIS CLUB SPORTING", with: "TCS")
clubName = clubName?.replacingOccurrences(of: "SPORTING CLUB", with: "SC")
clubName = clubName?.replacingOccurrences(of: "SPORTING ", with: "")
clubName = clubName?.replacingOccurrences(of: "TENNIS CLUB", with: "TC")
clubName = clubName?.replaceCharactersFromSet(characterSet: .punctuationCharacters)
return clubName
}
}

@ -0,0 +1,585 @@
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let federalTournament = try? JSONDecoder().decode(FederalTournament.self, from: jsonData)
import Foundation
import CoreLocation
enum DayPeriod {
case all
case weekend
case week
}
// MARK: - FederalTournament
struct FederalTournament: Identifiable, Codable {
func getEvent() -> Event {
var club = DataStore.shared.clubs.first(where: { $0.code == codeClub })
if club == nil {
club = Club(name: clubLabel(), code: codeClub)
try? DataStore.shared.clubs.addOrUpdate(instance: club!)
}
var event = DataStore.shared.events.first(where: { $0.tenupId == id.string })
if event == nil {
event = Event(club: club?.id, name: libelle, tenupId: id.string)
try? DataStore.shared.events.addOrUpdate(instance: event!)
}
return event!
}
static func sectionedData(from federalTournaments: [FederalTournament]) -> [String: [FederalTournament]] {
Dictionary(grouping: federalTournaments) { federalTournament in
URL.importDateFormatter.string(from: (federalTournament.dateDebut ?? .distantFuture))
}
}
let id: Int
var millesime: Int?
var libelle: String?
var tmc: Bool?
var tarifAdulteChampionnat: Double?
var type: String?
var ageReel: Bool?
var naturesTerrains: [JSONAny]?
var idsArbitres: [JSONAny]?
var tarifJeuneChampionnat: Double?
var international, inscriptionEnLigne: Bool?
var categorieTournoi: CategorieTournoi?
var prixLot: Int?
var paiementEnLigne: Bool?
var reductionAdherentJeune, reductionAdherentAdulte: Double?
var paiementEnLigneObligatoire: Bool?
var villeEngagement: String?
var senior, veteran, inscriptionEnLigneEnCours, avecResultatPublie: Bool?
var code: String?
var categorieAge: CategorieAge?
var codeComite: String?
var installations: [JSONAny]?
var reductionEpreuveSupplementaireJeune, reductionEpreuveSupplementaireAdulte: Double?
var nomComite: String?
var naturesEpreuves: [Serie]?
var jeune: Bool?
var courrielEngagement, nomClub: String?
var installation: Installation?
var categorieAgeMax: CategorieAge?
var tournoiInterne: Bool?
var nomLigue, nomEngagement, codeLigue: String?
var modeleDeBalle: ModeleDeBalle?
var jugeArbitre: JugeArbitre?
var adresse2Engagement: String?
var epreuves: [Epreuve]?
var dateDebut: Date?
var serie: Serie?
var dateFin, dateValidation: Date?
var codePostalEngagement, codeClub: String?
var prixEspece: Int?
var distanceEnMetres: Double?
var dayPeriod: DayPeriod {
if let dateDebut {
let day = dateDebut.get(.weekday)
switch day {
case 2...6:
return .week
default:
return .weekend
}
}
return .all
}
var dayDuration: Int {
if let dateDebut, let dateFin {
return Calendar.current.numberOfDaysBetween(dateDebut, and: dateFin) + 1
} else {
return 1
}
}
var city: String? {
if let installation { return installation.ville }
return nil
}
var location: CLLocation? {
if let lat = installation?.lat, let lng = installation?.lng {
return CLLocation(latitude: lat, longitude: lng)
}
return nil
}
var computedStartDate: String? {
dateDebut?.formatted(.dateTime.day(.defaultDigits).month(.abbreviated).year())
}
var tournaments: [any TournamentBuildHolder] {
epreuves?
.compactMap({ $0.tournamentBuild })
.sorted(using:
.keyPath(\TournamentBuild.level.order),
.keyPath(\TournamentBuild.category.order),
.keyPath(\TournamentBuild.age.order))
?? []
}
var shareMessage: String {
[libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n"
}
func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool {
var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current)
trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ")
trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .symbols, replacementString: " ")
switch scope {
case .club:
return nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
case .all:
return libelle?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || jugeArbitre?.nom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true ||
jugeArbitre?.prenom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
case .ligue:
return nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
}
}
}
extension FederalTournament: FederalTournamentHolder {
var startDate: Date { dateDebut ?? .distantFuture }
var endDate: Date? { dateFin }
// var distance: Double? { distanceEnMetres }
// var localizedLabel: String? { libelle }
// var clubName: String? { nomClub }
// var importedId: Int { id }
var holderId: String { id.string }
func clubLabel() -> String {
nomClub ?? villeEngagement ?? installation?.nom ?? ""
}
func subtitleLabel() -> String {
""
}
}
// MARK: - CategorieAge
struct CategorieAge: Codable {
var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int?
var categoriesAgeTypePratique: [CategoriesAgeTypePratique]?
var ageMax: Int?
var libelle: String?
var id: Int?
var valide, ageReel: Bool?
var ageRechercheMin: Int?
var homologable: Bool?
var tournamentAge: FederalTournamentAge? {
if let id {
return FederalTournamentAge(rawValue: id)
}
if let libelle {
return FederalTournamentAge.allCases.first(where: { $0.localizedLabel().localizedCaseInsensitiveContains(libelle) })
}
return nil
}
}
// MARK: - CategoriesAgeTypePratique
struct CategoriesAgeTypePratique: Codable {
var id: ID?
}
// MARK: - ID
struct ID: Codable {
var typePratique: TypePratique?
var idCategorieAge: Int?
}
enum TypePratique: String, Codable {
case beach = "BEACH"
case padel = "PADEL"
case tennis = "TENNIS"
}
// MARK: - CategorieTournoi
struct CategorieTournoi: Codable {
var code, codeTaxe: String?
var compteurGda: CompteurGda?
var libelle, niveauHierarchique: String?
var valide: Bool?
}
// MARK: - CompteurGda
struct CompteurGda: Codable {
var classementMax: Classement?
var libelle: String?
var classementMin: Classement?
}
// MARK: - Classement
struct Classement: Codable {
var nature, libelle: String?
var serie: Serie?
var sexe: String?
var id: Int?
var valide: Bool?
var poidsDouble, echelon: Int?
}
// MARK: - Serie
struct Serie: Codable {
var code, libelle: String?
var valide: Bool?
var sexe: String?
var tournamentCategory: TournamentCategory? {
TournamentCategory.allCases.first(where: { $0.requestLabel == code })
}
}
// MARK: - Epreuve
struct Epreuve: Codable {
var inscriptionEnLigneEnCours: Bool?
var categorieAge: CategorieAge?
var typeEpreuve: TypeEpreuve?
var tarifAdulte: Double?
var classementHaut: Classement?
var nombreDecoupagesPublies: Int?
var libelle: String?
var participationAgeReel, clotureInscriptionSansPreavis: Bool?
var dateCloture: Date?
var publicationResultat: Bool?
var classementBas: Classement?
var tarifsSpecifiques: [JSONAny]?
var homologuee: Bool?
var dateDebut: Date?
var tarifJeune: Double?
var serie: Serie?
var categorieAgeMax: CategorieAge?
var dateFin: Date?
var borneAnneesNaissance: BorneAnneesNaissance?
var natureEpreuve: Serie?
var epreuveOpen: Bool?
enum CodingKeys: String, CodingKey {
case inscriptionEnLigneEnCours, categorieAge, tarifAdulte, classementHaut, nombreDecoupagesPublies, libelle, participationAgeReel, clotureInscriptionSansPreavis, dateCloture, publicationResultat, classementBas, tarifsSpecifiques, homologuee, dateDebut, tarifJeune, serie, categorieAgeMax, dateFin, borneAnneesNaissance, natureEpreuve, typeEpreuve
case epreuveOpen = "open"
}
var tournamentBuild: TournamentBuild? {
if let age = categorieAge?.tournamentAge, let category = natureEpreuve?.tournamentCategory, let level = typeEpreuve?.tournamentLevel {
return TournamentBuild(category: category, level: level, age: age)
}
return nil
}
}
// MARK: - TypeEpreuve
struct TypeEpreuve: Codable {
let code: String?
let delai: Int?
let libelle: String?
let coefficient, montantMax, montantMin: Int?
let valide: Bool?
let echelon: Int?
let typeHomologation: String?
var tournamentLevel: TournamentLevel? {
if let code, let value = Int(code.removingFirstCharacter) {
return TournamentLevel(rawValue: value)
}
return nil
}
}
// MARK: - BorneAnneesNaissance
struct BorneAnneesNaissance: Codable {
var min, max: Int?
}
// MARK: - Installation
struct Installation: Codable {
var ville: String?
var lng: Double?
var surfaces: [JSONAny]?
var vestiaires: Bool?
var telephone, codePostal, nom, adresse1, adresse2: String?
var lat: Double?
var clubHouse: Bool?
var libelle: String {
[adresse1, adresse2, ville, codePostal].compactMap({ $0 }).joined(separator: " ")
}
}
// MARK: - JugeArbitre
struct JugeArbitre: Codable {
var idCRM, id: Int?
var nom, prenom: String?
enum CodingKeys: String, CodingKey {
case idCRM = "idCrm"
case id, nom, prenom
}
}
// MARK: - ModeleDeBalle
struct ModeleDeBalle: Codable {
var libelle: String?
var marqueDeBalle: MarqueDeBalle?
var id: Int?
var valide, homologue: Bool?
}
// MARK: - MarqueDeBalle
struct MarqueDeBalle: Codable {
var id: Int?
var valide: Bool?
var marque: String?
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}

@ -0,0 +1,30 @@
//
// FederalTournamentHolder.swift
// Padel Tournament
//
// Created by Razmig Sarkissian on 13/11/2023.
//
import Foundation
protocol FederalTournamentHolder {
var holderId: String { get }
var startDate: Date { get }
var endDate: Date? { get }
var tournaments: [any TournamentBuildHolder] { get }
func clubLabel() -> String
func subtitleLabel() -> String
var dayDuration: Int { get }
}
extension FederalTournamentHolder {
func durationLabel() -> String {
switch dayDuration {
case 1:
return (startDate.timeOfDay == .evening ? "Soir" : "Journée")
default:
return dayDuration.formatted() + " jours"
}
}
}

@ -109,7 +109,12 @@ class Tournament : ModelObject, Storable {
func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String { func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let club = club() { if let club = club() {
return "@" + club.acronym switch displayStyle {
case .wide:
return club.name
case .short:
return club.acronym
}
} else { } else {
return "" return ""
} }
@ -636,6 +641,12 @@ class Tournament : ModelObject, Storable {
} }
} }
func isSameBuild(_ build: any TournamentBuildHolder) -> Bool {
tournamentLevel == build.level
&& tournamentCategory == build.category
&& federalTournamentAge == build.age
}
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.registrationDate!), .keyPath(\.canonicalName)] private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.registrationDate!), .keyPath(\.canonicalName)]
override func deleteDependencies() throws { override func deleteDependencies() throws {
@ -688,35 +699,39 @@ extension Tournament: Hashable {
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(id) hasher.combine(id)
hasher.combine(event)
hasher.combine(creator)
hasher.combine(name)
hasher.combine(startDate)
hasher.combine(endDate)
hasher.combine(creationDate)
hasher.combine(isPrivate)
hasher.combine(groupStageFormat)
hasher.combine(roundFormat)
hasher.combine(loserRoundFormat)
hasher.combine(groupStageSortMode)
hasher.combine(groupStageCount)
hasher.combine(rankSourceDate)
hasher.combine(dayDuration)
hasher.combine(teamCount)
hasher.combine(teamSorting)
hasher.combine(federalCategory)
hasher.combine(federalLevelCategory)
hasher.combine(federalAgeCategory)
hasher.combine(groupStageCourtCount)
hasher.combine(seedCount)
hasher.combine(closedRegistrationDate)
hasher.combine(groupStageAdditionalQualified)
hasher.combine(courtCount)
hasher.combine(prioritizeClubMembers)
hasher.combine(qualifiedPerGroupStage)
hasher.combine(teamsPerGroupStage)
hasher.combine(entryFee)
hasher.combine(maleUnrankedValue)
hasher.combine(femaleUnrankedValue)
} }
} }
extension Tournament: FederalTournamentHolder {
var holderId: String { id }
func clubLabel() -> String {
locationLabel()
}
func subtitleLabel() -> String {
subtitle()
}
var tournaments: [any TournamentBuildHolder] {
[
self
]
}
}
extension Tournament: TournamentBuildHolder {
var category: TournamentCategory {
tournamentCategory
}
var level: TournamentLevel {
tournamentLevel
}
var age: FederalTournamentAge {
federalTournamentAge
}
}

@ -14,13 +14,15 @@ enum UserRight: Int, Codable {
case creation = 2 case creation = 2
} }
@Observable
class User: UserBase { class User: UserBase {
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var username: String public var username: String
public var email: String? public var email: String?
var club_id: String? var club: String?
var umpire_code: String? var umpireCode: String?
var licenceId: String?
init(username: String, email: String? = nil) { init(username: String, email: String? = nil) {
self.username = username self.username = username
@ -34,6 +36,24 @@ class User: UserBase {
throw UUIDError.cantConvertString(string: self.id) throw UUIDError.cantConvertString(string: self.id)
} }
func currentPlayerData() -> ImportedPlayer? {
guard let licenceId else { return nil }
let federalContext = PersistenceController.shared.localContainer.viewContext
let fetchRequest = ImportedPlayer.fetchRequest()
let predicate = NSPredicate(format: "license == %@", licenceId)
fetchRequest.predicate = predicate
return try? federalContext.fetch(fetchRequest).first
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _username = "username"
case _email = "email"
case _club = "club"
case _umpireCode = "umpireCode"
case _licenceId = "licenceId"
}
} }
class UserCreationForm: User, UserPasswordBase { class UserCreationForm: User, UserPasswordBase {

@ -0,0 +1,26 @@
//
// Calendar+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 28/03/2024.
//
import Foundation
extension Calendar {
func numberOfDaysBetween(_ from: Date?, and to: Date?) -> Int {
guard let from, let to else { return 0 }
let fromDate = startOfDay(for: from)
let toDate = startOfDay(for: to)
let numberOfDays = dateComponents([.day], from: fromDate, to: toDate)
return numberOfDays.day! // <1>
}
func isSameDay(date1: Date?, date2: Date?) -> Bool {
guard let date1, let date2 else { return false }
return numberOfDaysBetween(date1, and: date2) == 0
}
}

@ -6,11 +6,55 @@
// //
import Foundation import Foundation
enum TimeOfDay {
case morning
case noon
case afternoon
case evening
case night
var hello: String {
switch self {
case .morning, .noon, .afternoon:
return "Bonjour"
case .evening, .night:
return "Bonsoir"
}
}
var goodbye: String {
switch self {
case .morning, .noon, .afternoon:
return "Bonne journée"
case .evening, .night:
return "Bonne soirée"
}
}
}
extension Date { extension Date {
var monthYearFormatted: String { var monthYearFormatted: String {
formatted(.dateTime.month(.wide).year(.defaultDigits)) formatted(.dateTime.month(.wide).year(.defaultDigits))
} }
var twoDigitsYearFormatted: String {
formatted(Date.FormatStyle(date: .numeric, time: .omitted).locale(Locale(identifier: "fr_FR")).year(.twoDigits))
}
var timeOfDay: TimeOfDay {
let hour = Calendar.current.component(.hour, from: self)
switch hour {
case 6..<12 : return .morning
case 12 : return .noon
case 13..<17 : return .afternoon
case 17..<22 : return .evening
default: return .night
}
}
} }
extension Date { extension Date {
@ -40,5 +84,100 @@ extension Date {
return Calendar.current.date(byAdding: .hour, value: 9, to: date!)! return Calendar.current.date(byAdding: .hour, value: 9, to: date!)!
} }
} }
static var firstDayOfWeek = Calendar.current.firstWeekday
static var capitalizedFirstLettersOfWeekdays: [String] {
let calendar = Calendar.current
// let weekdays = calendar.shortWeekdaySymbols
// return weekdays.map { weekday in
// guard let firstLetter = weekday.first else { return "" }
// return String(firstLetter).capitalized
// }
// Adjusted for the different weekday starts
var weekdays = calendar.veryShortStandaloneWeekdaySymbols
if firstDayOfWeek > 1 {
for _ in 1..<firstDayOfWeek {
if let first = weekdays.first {
weekdays.append(first)
weekdays.removeFirst()
}
}
}
return weekdays.map { $0.capitalized }
}
static var fullMonthNames: [String] {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
return (1...12).compactMap { month in
dateFormatter.setLocalizedDateFormatFromTemplate("MMMM")
let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1))
return date.map { dateFormatter.string(from: $0) }
}
}
var startOfMonth: Date {
Calendar.current.dateInterval(of: .month, for: self)!.start
}
var endOfMonth: Date {
let lastDay = Calendar.current.dateInterval(of: .month, for: self)!.end
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)!
}
var startOfPreviousMonth: Date {
let dayInPreviousMonth = Calendar.current.date(byAdding: .month, value: -1, to: self)!
return dayInPreviousMonth.startOfMonth
}
var numberOfDaysInMonth: Int {
Calendar.current.component(.day, from: endOfMonth)
}
// var sundayBeforeStart: Date {
// let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth)
// let numberFromPreviousMonth = startOfMonthWeekday - 1
// return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)!
// }
// New to accomodate for different start of week days
var firstWeekDayBeforeStart: Date {
let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth)
let numberFromPreviousMonth = startOfMonthWeekday - Self.firstDayOfWeek
return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)!
}
var calendarDisplayDays: [Date] {
var days: [Date] = []
// Current month days
for dayOffset in 0..<numberOfDaysInMonth {
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfMonth)
days.append(newDay!)
}
// previous month days
for dayOffset in 0..<startOfPreviousMonth.numberOfDaysInMonth {
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfPreviousMonth)
days.append(newDay!)
}
// Fixed to accomodate different weekday starts
return days.filter { $0 >= firstWeekDayBeforeStart && $0 <= endOfMonth }.sorted(by: <)
}
var monthInt: Int {
Calendar.current.component(.month, from: self)
}
var yearInt: Int {
Calendar.current.component(.year, from: self)
}
var dayInt: Int {
Calendar.current.component(.day, from: self)
}
var startOfDay: Date {
Calendar.current.startOfDay(for: self)
}
} }

@ -23,6 +23,10 @@ extension String {
var canonicalVersionWithPunctuation: String { var canonicalVersionWithPunctuation: String {
trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased() trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased()
} }
var removingFirstCharacter: String {
String(dropFirst())
}
} }
extension String { extension String {
@ -133,3 +137,7 @@ extension String {
return matches.map { String(self[$0.range]) } return matches.map { String(self[$0.range]) }
} }
} }
extension LosslessStringConvertible {
var string: String { .init(self) }
}

@ -9,6 +9,19 @@ import Foundation
import CoreLocation import CoreLocation
class NetworkFederalService { class NetworkFederalService {
struct HttpCommand: Decodable {
let command: String
let results: HttpResults?
let method : String?
let selector : String?
let data: String?
}
struct HttpResults: Decodable {
let nb_results : Int
let items: [FederalTournament]?
}
static let shared: NetworkFederalService = NetworkFederalService() static let shared: NetworkFederalService = NetworkFederalService()
var formId = "" var formId = ""
@ -78,4 +91,101 @@ class NetworkFederalService {
return try await runTenupTask(request: request) return try await runTenupTask(request: request)
} }
func getClubFederalTournaments(page: Int, tournaments: [FederalTournament], club: String, codeClub: String, startDate: Date? = nil) async -> [FederalTournament] {
if formId.isEmpty {
do {
try await getNewBuildForm()
} catch {
print("getClubFederalTournaments", error)
}
}
var dateComponent = ""
if let startDate {
dateComponent = "&date[start]=\(startDate.twoDigitsYearFormatted)"
}
let parameters = """
recherche_type=club&club[autocomplete][value_container][value_field]=\(codeClub.replaceCharactersFromSet(characterSet: .whitespaces))&club[autocomplete][value_container][label_field]=\(club.replaceCharactersFromSet(characterSet: .whitespaces, replacementString: "+"))&pratique=PADEL\(dateComponent)&page=\(page)&sort=dateDebut+asc&form_build_id=\(formId)&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page
"""
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://tenup.fft.fr/system/ajax")!,timeoutInterval: Double.infinity)
request.addValue("application/json, text/javascript, */*; q=0.01", forHTTPHeaderField: "Accept")
request.addValue("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", forHTTPHeaderField: "Accept-Language")
request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")
request.addValue("application/x-www-form-urlencoded; charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.addValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With")
request.addValue("https://tenup.fft.fr", forHTTPHeaderField: "Origin")
request.addValue("keep-alive", forHTTPHeaderField: "Connection")
request.addValue("https://tenup.fft.fr/recherche/tournois", forHTTPHeaderField: "Referer")
request.addValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest")
request.addValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode")
request.addValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site")
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
}
}
return finalTournaments
}
} catch {
print("getClubFederalTournaments", error)
}
return []
}
func getNewBuildForm() async throws {
var request = URLRequest(url: URL(string: "https://tenup.fft.fr/recherche/tournois")!,timeoutInterval: Double.infinity)
request.addValue("application/json, text/javascript, */*; q=0.01", forHTTPHeaderField: "Accept")
request.addValue("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", forHTTPHeaderField: "Accept-Language")
request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")
request.addValue("application/x-www-form-urlencoded; charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.addValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With")
request.addValue("https://tenup.fft.fr", forHTTPHeaderField: "Origin")
request.addValue("keep-alive", forHTTPHeaderField: "Connection")
request.addValue("https://tenup.fft.fr/recherche/tournois", forHTTPHeaderField: "Referer")
request.addValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest")
request.addValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode")
request.addValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site")
request.addValue("trailers", forHTTPHeaderField: "TE")
request.httpMethod = "GET"
let task = try await URLSession.shared.data(for: request)
if let stringData = String(data: task.0, encoding: .utf8) {
let stringDataFolded = stringData.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
let prefix = "form_build_id\"value=\"form-"
var finalData = ""
if let lab = stringDataFolded.matches(of: try! Regex("\(prefix)")).last {
finalData = String(stringDataFolded[lab.range.upperBound...])
}
let suffix = "\"/><inputtype=\"hidden\"name=\"form_id\"value=\"recherche_tournois_form"
if let suff = finalData.firstMatch(of: try! Regex("\(suffix)")) {
finalData = String(finalData[..<suff.range.lowerBound])
}
print(finalData)
formId = "form-\(finalData)"
} else {
print("no data found in html")
}
}
} }

@ -24,14 +24,21 @@ enum RankSource: Hashable {
} }
} }
struct TournamentBuild: Hashable, Codable, Identifiable { protocol TournamentBuildHolder: Identifiable {
var id: String { get }
var category: TournamentCategory { get }
var level: TournamentLevel { get }
var age: FederalTournamentAge { get }
}
struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
var id: String { identifier } var id: String { identifier }
let category: TournamentCategory let category: TournamentCategory
let level: TournamentLevel let level: TournamentLevel
let age: FederalTournamentAge let age: FederalTournamentAge
var japIdentifier: Int? = nil // var japIdentifier: Int? = nil
var japFirstName: String? = nil // var japFirstName: String? = nil
var japLastName: String? = nil // var japLastName: String? = nil
var identifier: String { var identifier: String {
level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel()

@ -16,6 +16,7 @@ struct PadelClubApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
MainView() MainView()
.accentColor(.launchScreenBackground)
.onAppear { .onAppear {
self._onAppear() self._onAppear()
} }

@ -12,6 +12,12 @@ enum AgendaDestination: CaseIterable, Identifiable {
case activity case activity
case history case history
case tenup
enum ViewStyle {
case list
case calendar
}
var localizedTitleKey: String { var localizedTitleKey: String {
switch self { switch self {
@ -19,6 +25,8 @@ enum AgendaDestination: CaseIterable, Identifiable {
return "En cours" return "En cours"
case .history: case .history:
return "Terminé" return "Terminé"
case .tenup:
return "Tenup"
} }
} }
@ -28,6 +36,8 @@ enum AgendaDestination: CaseIterable, Identifiable {
return "squares.leading.rectangle" return "squares.leading.rectangle"
case .history: case .history:
return "book.closed" return "book.closed"
case .tenup:
return "tennisball"
} }
} }
} }

@ -0,0 +1,14 @@
//
// FederalTournamentSearchScope.swift
// PadelClub
//
// Created by Razmig Sarkissian on 28/03/2024.
//
import Foundation
enum FederalTournamentSearchScope: String, CaseIterable {
case all = "Tout"
case club = "Club"
case ligue = "Ligue"
}

@ -39,7 +39,7 @@ enum TabDestination: CaseIterable, Identifiable {
var image: String { var image: String {
switch self { switch self {
case .activity: case .activity:
return "calendar" return "calendar.day.timeline.left"
case .eventList: case .eventList:
return "book.closed" return "book.closed"
case .toolbox: case .toolbox:

@ -18,6 +18,14 @@ struct ClubsView: View {
var body: some View { var body: some View {
List { List {
if dataStore.clubs.isEmpty == false && selection == nil {
Section {
TipView(tip)
.tipStyle(tint: nil)
}
}
ForEach(dataStore.clubs) { club in ForEach(dataStore.clubs) { club in
if let selection { if let selection {
Button { Button {
@ -44,13 +52,6 @@ struct ClubsView: View {
} }
} }
} }
Section {
if dataStore.clubs.isEmpty == false {
TipView(tip)
.tipStyle(tint: nil)
}
}
} }
.overlay { .overlay {
if dataStore.clubs.isEmpty { if dataStore.clubs.isEmpty {

@ -16,29 +16,37 @@ struct ActivityView: View {
@State private var filterEnabled: Bool = false @State private var filterEnabled: Bool = false
@State private var presentToolbar: Bool = false @State private var presentToolbar: Bool = false
@State private var newTournament: Tournament? @State private var newTournament: Tournament?
@State private var viewStyle: AgendaDestination.ViewStyle = .list
@State private var federalTournaments: [FederalTournament] = []
@State private var isGatheringFederalTournaments: Bool = false
@Binding var selectedTab: TabDestination?
var runningTournaments: [Tournament] { var runningTournaments: [FederalTournamentHolder] {
dataStore.tournaments.filter({ $0.endDate == nil }).sorted(by: \.startDate) dataStore.tournaments.filter({ $0.endDate == nil })
} }
var endedTournaments: [Tournament] { var endedTournaments: [Tournament] {
dataStore.tournaments.filter({ $0.endDate != nil }).sorted(using: SortDescriptor(\.startDate, order: .reverse)) dataStore.tournaments.filter({ $0.endDate != nil }).sorted(using: SortDescriptor(\.startDate, order: .reverse))
} }
var tournaments: [Tournament] { func _activityStatus() -> String? {
let tournaments = tournaments
if tournaments.isEmpty && filterEnabled == false {
return nil
} else {
let count = tournaments.map { $0.tournaments.count }.reduce(0,+)
return "\(count) tournoi" + count.pluralSuffix
}
}
var tournaments: [FederalTournamentHolder] {
switch agendaDestination { switch agendaDestination {
case .activity: case .activity:
runningTournaments runningTournaments
case .history: case .history:
endedTournaments endedTournaments
} case .tenup:
} federalTournaments
func _activityStatus() -> String {
if tournaments.isEmpty && filterEnabled == false {
"Aucune activité"
} else {
"\(tournaments.count) tournois"
} }
} }
@ -49,6 +57,19 @@ struct ActivityView: View {
AgendaDestinationPickerView(agendaDestination: $agendaDestination) AgendaDestinationPickerView(agendaDestination: $agendaDestination)
} }
switch agendaDestination {
case .activity:
EventListView(tournaments: runningTournaments, viewStyle: viewStyle)
case .history:
EventListView(tournaments: endedTournaments, viewStyle: viewStyle)
case .tenup:
EventListView(tournaments: federalTournaments, viewStyle: viewStyle)
}
}
.overlay {
if isGatheringFederalTournaments {
ProgressView()
} else {
if tournaments.isEmpty { if tournaments.isEmpty {
if searchText.isEmpty == false { if searchText.isEmpty == false {
ContentUnavailableView.search(text: searchText) ContentUnavailableView.search(text: searchText)
@ -63,55 +84,79 @@ struct ActivityView: View {
} }
} }
} else { } else {
ContentUnavailableView { _dataEmptyView()
Label("Aucune activité", systemImage: "shield.slash")
} description: {
Text("Aucun événement n'est prévu dans votre agenda.")
} actions: {
RowButtonView(title: "Créer un nouvel évenement") {
newTournament = Tournament.newEmptyInstance()
} }
RowButtonView(title: "Importer vos tournois Tenup") {
} }
} }
} }
} else { //.searchable(text: $searchText)
EventListView(tournaments: tournaments)
}
}
.searchable(text: $searchText)
.onAppear { presentToolbar = true } .onAppear { presentToolbar = true }
.onDisappear { presentToolbar = false } .onDisappear { presentToolbar = false }
.sheet(item: $newTournament) { tournament in .sheet(item: $newTournament) { tournament in
EventCreationView(tournaments: [tournament]) EventCreationView(tournaments: [tournament])
} }
.refreshable {
if agendaDestination == .tenup {
federalTournaments.removeAll()
_gatherFederalTournaments()
}
}
.task {
if agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.onChange(of: agendaDestination) {
if agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.toolbar { .toolbar {
if presentToolbar { if presentToolbar {
ToolbarItem(placement: .status) { ToolbarItem(placement: .status) {
VStack { VStack(spacing: -2) {
if filterEnabled { if filterEnabled {
Text("filtre actif") Text("filtre actif")
.font(.footnote)
} }
if let _activityStatus = _activityStatus() {
Text(_activityStatus()) Text(_activityStatus)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.font(.footnote)
} }
} }
.font(.footnote)
}
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
Button { Button {
filterEnabled.toggle() switch viewStyle {
case .list:
viewStyle = .calendar
case .calendar:
viewStyle = .list
}
} label: { } label: {
Image(systemName: "line.3.horizontal.decrease.circle") Image(systemName: "calendar.circle")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(minHeight: 28) .frame(minHeight: 28)
} }
.symbolVariant(filterEnabled ? .fill : .none) .symbolVariant(viewStyle == .calendar ? .fill : .none)
//
// Button {
// filterEnabled.toggle()
// } label: {
// Image(systemName: "line.3.horizontal.decrease.circle")
// .resizable()
// .scaledToFit()
// .frame(minHeight: 28)
// }
// .symbolVariant(filterEnabled ? .fill : .none)
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
@ -134,9 +179,87 @@ 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)
}
isGatheringFederalTournaments = false
}
}
@ViewBuilder
private func _dataEmptyView() -> some View {
switch agendaDestination {
case .activity:
_runningEmptyView()
case .history:
_endedEmptyView()
case .tenup:
_tenupEmptyView()
}
}
private func _runningEmptyView() -> some View {
ContentUnavailableView {
Label {
Text("Bienvenue sur Padel Club")
.font(.largeTitle)
} icon: {
Image(.padelClubLogoFondclairTransparent)
.resizable()
.scaledToFit()
.frame(width: 128)
}
} description: {
Text("Aucun événement en cours ou à venir dans votre agenda.")
} actions: {
RowButtonView(title: "Créer un nouvel événement") {
newTournament = Tournament.newEmptyInstance()
}
RowButtonView(title: "Importer via Tenup") {
agendaDestination = .tenup
}
}
}
private func _endedEmptyView() -> some View {
ContentUnavailableView {
Label("Aucun tournoi terminé", systemImage: "shield.slash")
} description: {
Text("Vos tournois terminés seront listés ici.")
}
}
private func _tenupEmptyView() -> some View {
if dataStore.clubs.isEmpty {
ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash")
} 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") {
selectedTab = .umpire
}
}
} else {
ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash")
} description: {
Text("Aucun tournoi n'a pu être récupéré via tenup.")
} actions: {
RowButtonView(title: "Rafraîchir") {
_gatherFederalTournaments()
}
}
}
}
} }
#Preview { #Preview {
ActivityView() ActivityView(selectedTab: .constant(.activity))
.environmentObject(DataStore.shared) .environmentObject(DataStore.shared)
} }

@ -0,0 +1,120 @@
//
// Created for Custom Calendar
// by Stewart Lynch on 2024-01-22
//
// Follow me on Mastodon: @StewartLynch@iosdev.space
// Follow me on Threads: @StewartLynch (https://www.threads.net)
// Follow me on X: https://x.com/StewartLynch
// Follow me on LinkedIn: https://linkedin.com/in/StewartLynch
// Subscribe on YouTube: https://youTube.com/@StewartLynch
// Buy me a ko-fi: https://ko-fi.com/StewartLynch
import SwiftUI
struct CalendarView: View {
let date: Date
let tournaments: [FederalTournamentHolder]
let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays
let columns = Array(repeating: GridItem(.flexible()), count: 7)
@State private var days: [Date] = []
@State private var counts = [Int : Int]()
// Extension properties to display selected day's workouts
@State private var selectedDay: Date?
@State private var tournamentsByDay: [FederalTournamentHolder] = []
@ViewBuilder
var body: some View {
let color = Color.blue
VStack {
HStack {
ForEach(daysOfWeek.indices, id: \.self) { index in
Text(daysOfWeek[index])
.fontWeight(.black)
.foregroundStyle(color)
.frame(maxWidth: .infinity)
}
}
LazyVGrid(columns: columns) {
ForEach(days, id: \.self) { day in
if day.monthInt != date.monthInt {
Text("")
} else {
Text(day.formatted(.dateTime.day()))
.fontWeight(.bold)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, minHeight: 40)
.background(
Circle()
.foregroundStyle(
Date.now.startOfDay == day.startOfDay
? .red.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3)
: color.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3)
)
)
.overlay(alignment: .bottomTrailing) {
if let count = counts[day.dayInt] {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
.foregroundColor(.secondary)
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 5, y: 5)
}
}
.onTapGesture {
// Used in the ExtendedProject branch
if let count = counts[day.dayInt], count > 0 {
selectedDay = day
} else {
selectedDay = nil
}
}
}
}
}
}
.onAppear {
days = date.calendarDisplayDays
setupCounts()
selectedDay = nil // Used to present list of workouts when tapped
}
.onChange(of: date) {
days = date.calendarDisplayDays
setupCounts()
selectedDay = nil // Used to present list of workouts when tapped
}
.onChange(of: selectedDay) {
// Will filter the workouts for the specific day and activity is selected
if let selectedDay {
tournamentsByDay = tournaments.filter {$0.startDate.dayInt == selectedDay.dayInt}
}
}
//
// if let selectedDay {
// // Presents the list of workouts for the selected day and activity
// ForEach(tournamentsByDay) { tournament in
// NavigationLink(value: tournament) {
// HStack {
// Text(tournament.title())
// Spacer()
// Text(tournament.sortedTeams().count.formatted())
// }
// }
// }
// }
}
func setupCounts() {
let filteredTournaments = tournaments
let mappedItems = filteredTournaments.map{($0.startDate.dayInt, $0.tournaments.count)}
counts = Dictionary(mappedItems, uniquingKeysWith: +)
}
}
#Preview {
CalendarView(date: .now, tournaments: [])
}

@ -8,27 +8,52 @@
import SwiftUI import SwiftUI
struct EventListView: View { struct EventListView: View {
@EnvironmentObject var dataStore: DataStore
let tournaments: [Tournament] let tournaments: [FederalTournamentHolder]
let viewStyle: AgendaDestination.ViewStyle
var body: some View { var body: some View {
let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.monthYearFormatted } let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.startOfMonth }
ForEach(groupedTournamentsByDate.keys.sorted(by: >), id: \.self) { section in ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) { if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) {
Section { Section {
ForEach(_tournaments) { tournament in switch viewStyle {
NavigationLink(value: tournament) { case .list:
_listView(_tournaments)
case .calendar:
CalendarView(date: section, tournaments: _tournaments)
}
} header: {
HStack { HStack {
TournamentCellView(tournament: tournament) Text(section.monthYearFormatted)
Spacer() Spacer()
Text(tournament.sortedTeams().count.formatted()) Text(_tournaments.map { $0.tournaments.count }.reduce(0,+).formatted())
.font(.largeTitle)
} }
} }
.headerProminence(.increased)
}
}
}
private func _listView(_ tournaments: [FederalTournamentHolder]) -> some View {
ForEach(tournaments, id: \.holderId) { tournamentHolder in
if let tournament = tournamentHolder as? Tournament {
_tournamentView(tournament)
} else if let federalTournament = tournamentHolder as? FederalTournament {
_federalTournamentView(federalTournament)
}
}
}
private func _tournamentView(_ tournament: Tournament) -> some View {
NavigationLink(value: tournament) {
TournamentCellView(tournament: tournament)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) { .swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) { Button(role: .destructive) {
try? DataStore.shared.tournaments.delete(instance: tournament) try? dataStore.tournaments.delete(instance: tournament)
} label: { } label: {
LabelDelete() LabelDelete()
} }
@ -41,54 +66,13 @@ struct EventListView: View {
} }
} }
} }
} header: {
HStack {
Text(section)
Spacer()
Text(_tournaments.count.formatted())
}
}
.headerProminence(.increased)
}
}
}
}
#Preview { private func _federalTournamentView(_ federalTournament: FederalTournament) -> some View {
EventListView(tournaments: []) TournamentCellView(tournament: federalTournament)
} }
/*
.overlay(alignment: .bottom) {
Button {
} label: {
Circle()
.fill(Color.white)
.frame(width: 64)
.overlay(
Image(systemName: "plus")
.font(.title2)
.padding()
)
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
}
.buttonStyle(.plain)
.padding()
} }
Button { #Preview {
} label: { EventListView(tournaments: [], viewStyle: .calendar)
RoundedRectangle(cornerRadius: 20, style: .continuous) }
.fill(Color.white)
.frame(height: 64)
.overlay(
Text("Créer un nouvel évenement")
.font(.title2)
.padding()
)
.shadow(radius: 10)
}
.buttonStyle(.plain)
.padding()
*/

@ -0,0 +1,59 @@
//
// TenupEventListView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 28/03/2024.
//
import SwiftUI
struct TenupEventListView: View {
@EnvironmentObject var dataStore: DataStore
let tournaments: [FederalTournament]
let viewStyle: AgendaDestination.ViewStyle
var body: some View {
let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.startOfMonth }
ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) {
Section {
switch viewStyle {
case .list:
_listView(_tournaments)
case .calendar:
CalendarView(date: section, tournaments: _tournaments)
}
} header: {
HStack {
Text(section.monthYearFormatted)
Spacer()
Text(_tournaments.map { $0.tournaments.count }.reduce(0,+).formatted())
}
}
.headerProminence(.increased)
}
}
}
private func _listView(_ tournaments: [FederalTournament]) -> some View {
ForEach(tournaments, id: \.holderId) { tournament in
TournamentCellView(tournament: tournament)
}
}
/*
Button("importer") {
let event = dataStore.events.first(where: { $0.tenupId == tournament.id.string }) ?? Event(tenupId: tournament.id.string)
let tournament = Tournament(event: event.id, )
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
*/
}
#Preview {
TenupEventListView(tournaments: [], viewStyle: .calendar)
}

@ -25,15 +25,11 @@ struct MainView: View {
animation: .default) animation: .default)
private var players: FetchedResults<ImportedPlayer> private var players: FetchedResults<ImportedPlayer>
@State private var selectedTab: TabDestination?
var body: some View { var body: some View {
TabView { TabView(selection: $selectedTab) {
if dataStore.tournaments.isEmpty { ActivityView(selectedTab: $selectedTab)
EmptyActivityView()
.tabItem(for: .activity) .tabItem(for: .activity)
} else {
ActivityView()
.tabItem(for: .activity)
}
TournamentOrganizerView() TournamentOrganizerView()
.tabItem(for: .tournamentOrganizer) .tabItem(for: .tournamentOrganizer)
ToolboxView() ToolboxView()
@ -58,7 +54,7 @@ struct MainView: View {
_activityStatusBoxView() _activityStatusBoxView()
} else { } else {
_activityStatusBoxView() _activityStatusBoxView()
.deferredRendering(for: .seconds(5)) .deferredRendering(for: .seconds(3))
} }
} }
} }
@ -66,7 +62,7 @@ struct MainView: View {
func _activityStatusBoxView() -> some View { func _activityStatusBoxView() -> some View {
_activityStatus() _activityStatus()
.font(.title3) .font(.title3)
.frame(height: 32) .frame(height: 28)
.padding() .padding()
.background { .background {
RoundedRectangle(cornerRadius: 20, style: .continuous) RoundedRectangle(cornerRadius: 20, style: .continuous)
@ -122,7 +118,7 @@ struct MainView: View {
} }
importingFiles = false importingFiles = false
_downloadPreviousDate() await _downloadPreviousDate()
} }
} }
@ -131,12 +127,10 @@ struct MainView: View {
lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false) lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false)
} }
private func _downloadPreviousDate() { private func _downloadPreviousDate() async {
Task {
await SourceFileManager.shared.getAllFiles() await SourceFileManager.shared.getAllFiles()
} }
} }
}
fileprivate extension View { fileprivate extension View {
func tabItem(for tabDestination: TabDestination) -> some View { func tabItem(for tabDestination: TabDestination) -> some View {
@ -152,6 +146,7 @@ fileprivate struct TabItemModifier: ViewModifier {
.tabItem { .tabItem {
Label(tabDestination.title, systemImage: tabDestination.image) Label(tabDestination.title, systemImage: tabDestination.image)
} }
.tag(tabDestination as TabDestination?)
} }
} }

@ -25,7 +25,7 @@ struct TournamentButtonView: View {
selectedId = tournament.id selectedId = tournament.id
} }
} label: { } label: {
TournamentCellView(tournament: tournament) TournamentCellView(tournament: tournament, displayStyle: .short)
.padding(8) .padding(8)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)

@ -8,11 +8,12 @@
import SwiftUI import SwiftUI
struct TournamentOrganizerView: View { struct TournamentOrganizerView: View {
@EnvironmentObject var dataStore: DataStore
@State private var selectedTournamentId: String? @State private var selectedTournamentId: String?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(DataStore.shared.tournaments) { tournament in ForEach(dataStore.tournaments) { tournament in
if tournament.id == selectedTournamentId { if tournament.id == selectedTournamentId {
OrganizedTournamentView(tournament: tournament) OrganizedTournamentView(tournament: tournament)
} }
@ -29,7 +30,7 @@ struct TournamentOrganizerView: View {
HStack { HStack {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack { HStack {
ForEach(DataStore.shared.tournaments) { tournament in ForEach(dataStore.tournaments) { tournament in
TournamentButtonView(tournament: tournament, selectedId: $selectedTournamentId) TournamentButtonView(tournament: tournament, selectedId: $selectedTournamentId)
} }
} }

@ -6,8 +6,10 @@
// //
import SwiftUI import SwiftUI
import CoreLocation
struct UmpireView: View { struct UmpireView: View {
@EnvironmentObject var dataStore: DataStore
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@ -23,14 +25,100 @@ struct UmpireView: View {
Label("Abonnement", systemImage: "tennisball.circle.fill") Label("Abonnement", systemImage: "tennisball.circle.fill")
} }
if let user = dataStore.user {
let currentPlayerData = user.currentPlayerData()
Section {
if let currentPlayerData {
NavigationLink {
} label: {
ImportedPlayerView(player: currentPlayerData)
}
} else {
NavigationLink {
SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in
if let player = players.first {
user.licenceId = player.license
let club = dataStore.clubs.first(where: { $0.code == player.clubCode }) ?? Club(name: player.clubName!, code: player.clubCode!)
try? dataStore.clubs.addOrUpdate(instance: club)
user.club = club.id
dataStore.setUser(user)
}
})
} label: {
Label("Ma fiche joueur", systemImage: "person.bust.circle.fill")
}
}
} header: {
if currentPlayerData != nil {
Text("Ma fiche joueur")
}
} footer: {
if user.licenceId == nil {
Text("Si vous avez participé à un tournoi dans les 12 derniers mois, Padel Club peut vous retrouver.")
} else {
Button("supprimer", role: .destructive) {
user.licenceId = nil
dataStore.setUser(user)
}
.font(.caption)
}
}
if let clubId = user.club {
if let club = dataStore.clubs.findById(clubId) {
Section {
NavigationLink {
ClubDetailView(club: club, displayContext: .edition)
} label: {
ClubRowView(club: club)
}
} header: {
Text("Mon club")
} footer: {
Button("supprimer", role: .destructive) {
user.club = nil
dataStore.setUser(user)
}
.font(.caption)
}
}
}
}
Section { Section {
NavigationLink { NavigationLink {
ClubsView() ClubsView()
} label: {
LabeledContent {
Text(dataStore.clubs.count.formatted())
} label: { } label: {
Label("Mes clubs favoris", systemImage: "house.and.flag.circle.fill") Label("Mes clubs favoris", systemImage: "house.and.flag.circle.fill")
} }
} }
} }
// Section {
// Text("Tenup ID")
// }
//
// Section {
// Text("Tournois")
// }
//
// Section {
// NavigationLink {
//
// } label: {
// Text("Favori")
// }
// NavigationLink {
//
// } label: {
// Text("Black list")
// }
// }
}
.navigationTitle("Juge-Arbitre") .navigationTitle("Juge-Arbitre")
} }
} }

@ -29,11 +29,13 @@ struct SelectablePlayerListView: View {
return URL.importDateFormatter.date(from: lastDataSource) return URL.importDateFormatter.date(from: lastDataSource)
} }
init(allowSelection: Int = 0, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) { init(allowSelection: Int = 0, searchField: String? = nil, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) {
self.allowSelection = allowSelection self.allowSelection = allowSelection
self.searchText = searchField ?? ""
self.playerSelectionAction = playerSelectionAction self.playerSelectionAction = playerSelectionAction
self.contentUnavailableAction = contentUnavailableAction self.contentUnavailableAction = contentUnavailableAction
let searchViewModel = SearchViewModel() let searchViewModel = SearchViewModel()
searchViewModel.searchText = searchField ?? ""
searchViewModel.isPresented = allowSelection != 0 searchViewModel.isPresented = allowSelection != 0
searchViewModel.user = user searchViewModel.user = user
searchViewModel.allowSelection = allowSelection searchViewModel.allowSelection = allowSelection

@ -9,6 +9,7 @@ import SwiftUI
struct DateBoxView: View { struct DateBoxView: View {
let date: Date let date: Date
var displayStyle: DisplayStyle = .wide
var body: some View { var body: some View {
VStack(alignment: .center, spacing: -2) { VStack(alignment: .center, spacing: -2) {
@ -16,7 +17,7 @@ struct DateBoxView: View {
.font(.caption2) .font(.caption2)
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
Text(date.formatted(.dateTime.day(.twoDigits))) Text(date.formatted(.dateTime.day(.twoDigits)))
.font(.title) .font(displayStyle == .wide ? .title : .title3)
.monospacedDigit() .monospacedDigit()
} }
Text(date.formatted(.dateTime.month(.abbreviated))) Text(date.formatted(.dateTime.month(.abbreviated)))

@ -0,0 +1,35 @@
//
// PadelClubButtonView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 29/03/2024.
//
import SwiftUI
struct PadelClubButtonView: View {
let isImported: Bool
var body: some View {
Image(.logo)
.resizable()
.scaledToFit()
.frame(width: 44)
.overlay(alignment: .bottomTrailing) {
if isImported {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
}
}
}
}
#Preview {
PadelClubButtonView(isImported: true)
}

@ -8,30 +8,91 @@
import SwiftUI import SwiftUI
struct TournamentCellView: View { struct TournamentCellView: View {
@EnvironmentObject var dataStore: DataStore
let tournament: Tournament let tournament: FederalTournamentHolder
let color: Color = .black let color: Color = .black
var displayStyle: DisplayStyle = .wide
var event: Event? {
guard let federalTournament = tournament as? FederalTournament else { return nil }
return dataStore.events.first(where: { $0.tenupId == federalTournament.id.string })
}
var body: some View { var body: some View {
HStack(alignment: .top) { ForEach(tournament.tournaments, id: \.id) { build in
DateBoxView(date: tournament.startDate) _buildView(build, alreadyExist: event?.existingBuild(build) != nil)
}
}
private func _buildView(_ build: any TournamentBuildHolder, alreadyExist: Bool) -> some View {
HStack {
DateBoxView(date: tournament.startDate, displayStyle: displayStyle)
Rectangle() Rectangle()
.fill(color) .fill(color)
.frame(width: 2) .frame(width: 2)
VStack(alignment: .leading, spacing: -2) { VStack(alignment: .leading, spacing: -2) {
Text(tournament.locationLabel()) if let tournament = tournament as? Tournament {
Text(tournament.locationLabel(displayStyle))
.font(.caption2) .font(.caption2)
} else {
Text(tournament.clubLabel())
.font(.caption2)
}
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
Text(tournament.tournamentLevel.localizedLabel()) Text(build.level.localizedLabel())
.font(.title) if displayStyle == .wide {
Text(tournament.subtitle()) VStack(alignment: .leading, spacing: 0) {
.font(.headline) Text(build.category.localizedLabel())
Text(build.age.localizedLabel())
} }
Text(tournament.tournamentCategory.localizedLabel())
.font(.caption2)
Text(tournament.federalTournamentAge.localizedLabel())
.font(.caption2) .font(.caption2)
} }
Spacer()
if let tournament = tournament as? Tournament, displayStyle == .wide {
Text(tournament.sortedTeams().count.formatted())
} else if let federalTournament = tournament as? FederalTournament {
Button {
if alreadyExist == false {
let event = federalTournament.getEvent()
let newTournament = Tournament.newEmptyInstance()
newTournament.event = event.id
newTournament.tournamentLevel = build.level
newTournament.tournamentCategory = build.category
newTournament.federalTournamentAge = build.age
newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate
try? dataStore.tournaments.addOrUpdate(instance: newTournament)
} else {
//event?.existingBuild(build)
}
} label: {
Image(systemName: alreadyExist ? "checkmark.circle.fill" : "square.and.arrow.down")
.resizable()
.scaledToFit()
.frame(height: 28)
.tint(alreadyExist ? Color.green : nil)
} }
.buttonStyle(.borderless)
}
}
.font(displayStyle == .wide ? .title : .title3)
if displayStyle == .wide {
HStack {
Text(tournament.durationLabel())
Spacer()
if let tournament = tournament as? Tournament {
Text("équipes")
}
}
Text(tournament.subtitleLabel())
} else {
Text(build.category.localizedLabel())
Text(build.age.localizedLabel())
}
}
}
.font(.caption2)
} }
} }

@ -12,8 +12,8 @@ struct LoginView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State var username: String = "laurent" @State var username: String = "razmig"
@State var password: String = "staxkikoo12" @State var password: String = "StaxKikoo12"
@State var error: Error? = nil @State var error: Error? = nil

Loading…
Cancel
Save