handle updates

multistore
Razmig Sarkissian 2 years ago
parent 9cf95398f8
commit 53756b3c58
  1. 204
      PadelClub.xcodeproj/project.pbxproj
  2. 18
      PadelClub/Data/DataStore.swift
  3. 8
      PadelClub/Data/GroupStage.swift
  4. 15
      PadelClub/Data/MockData.swift
  5. 119
      PadelClub/Data/PlayerRegistration.swift
  6. 77
      PadelClub/Data/TeamRegistration.swift
  7. 171
      PadelClub/Data/Tournament.swift
  8. 17
      PadelClub/Extensions/Sequence+Extensions.swift
  9. 4
      PadelClub/Extensions/String+Extensions.swift
  10. 39
      PadelClub/Extensions/URL+Extensions.swift
  11. 22
      PadelClub/Info.plist
  12. 242
      PadelClub/Manager/FileImportManager.swift
  13. 3
      PadelClub/Manager/Network/NetworkManager.swift
  14. 91
      PadelClub/Manager/SourceFileManager.swift
  15. 30
      PadelClub/Manager/SwiftParser.swift
  16. 4
      PadelClub/Manager/Tips.swift
  17. 2
      PadelClub/Views/Components/StepperView.swift
  18. 26
      PadelClub/Views/ContentView.swift
  19. 8
      PadelClub/Views/Event/EventCreationView.swift
  20. 7
      PadelClub/Views/GroupStage/GroupStageView.swift
  21. 55
      PadelClub/Views/Navigation/MainView.swift
  22. 2
      PadelClub/Views/Team/TeamDetailView.swift
  23. 285
      PadelClub/Views/Tournament/FileImportView.swift
  24. 4
      PadelClub/Views/Tournament/Screen/Components/InscriptionTipsView.swift
  25. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift
  26. 80
      PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift
  27. 199
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  28. 2
      PadelClub/Views/Tournament/TournamentView.swift

@ -37,6 +37,51 @@
FF089EB82BB00ABF00F0AEC7 /* InscriptionTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */; }; FF089EB82BB00ABF00F0AEC7 /* InscriptionTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */; };
FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; };
FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */; }; FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */; };
FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */; };
FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */; };
FF0EC5222BB173E70056B6D1 /* UpdateSourceRankDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */; };
FF0EC54E2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5382BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv */; };
FF0EC54F2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-08-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5482BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-08-2022.csv */; };
FF0EC5502BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-12-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5352BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-12-2022.csv */; };
FF0EC5512BB195E20056B6D1 /* CLASSEMENT PADEL DAMES-07-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5232BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-07-2023.csv */; };
FF0EC5522BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-02-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5412BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-02-2023.csv */; };
FF0EC5532BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv */; };
FF0EC5542BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv */; };
FF0EC5552BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-08-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5282BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-08-2023.csv */; };
FF0EC5562BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv */; };
FF0EC5572BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5332BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-10-2022.csv */; };
FF0EC5582BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5422BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv */; };
FF0EC5592BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-03-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-03-2023.csv */; };
FF0EC55A2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-07-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5272BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-07-2023.csv */; };
FF0EC55B2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-01-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-01-2023.csv */; };
FF0EC55C2BB195E20056B6D1 /* CLASSEMENT PADEL DAMES-08-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5242BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-08-2023.csv */; };
FF0EC55D2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-04-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-04-2023.csv */; };
FF0EC55E2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-05-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-05-2023.csv */; };
FF0EC55F2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5392BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv */; };
FF0EC5602BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-08-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52A2BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-08-2023.csv */; };
FF0EC5612BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-05-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5462BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-05-2023.csv */; };
FF0EC5622BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv */; };
FF0EC5632BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv */; };
FF0EC5642BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC53F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv */; };
FF0EC5652BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5402BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv */; };
FF0EC5662BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-02-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC52C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-02-2023.csv */; };
FF0EC5672BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-03-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5442BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-03-2023.csv */; };
FF0EC5682BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-06-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5302BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-06-2023.csv */; };
FF0EC5692BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv */; };
FF0EC56A2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */; };
FF0EC56B2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-06-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5472BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-06-2023.csv */; };
FF0EC56C2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-07-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5252BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-07-2023.csv */; };
FF0EC56D2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5432BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv */; };
FF0EC56E2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-08-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5262BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-08-2023.csv */; };
FF0EC56F2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-09-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5322BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-09-2022.csv */; };
FF0EC5702BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-08-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5312BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-08-2022.csv */; };
FF0EC5712BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-01-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5362BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-01-2023.csv */; };
FF0EC5722BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-09-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5492BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-09-2022.csv */; };
FF0EC5732BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-07-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5292BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-07-2023.csv */; };
FF0EC5742BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-01-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5372BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-01-2023.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 */; };
FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; };
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 */; };
@ -104,6 +149,8 @@
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 */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.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 */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
@ -114,7 +161,6 @@
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784032B91C280000F62A6 /* EmptyActivityView.swift */; }; FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784032B91C280000F62A6 /* EmptyActivityView.swift */; };
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; };
FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; }; FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; };
FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD12B9238C3008466FA /* FileImportManager.swift */; };
FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; }; FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; };
FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; }; FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; };
FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; }; FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; };
@ -201,6 +247,51 @@
FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionTipsView.swift; sourceTree = "<group>"; }; FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionTipsView.swift; sourceTree = "<group>"; };
FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPopoverView.swift; sourceTree = "<group>"; }; FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPopoverView.swift; sourceTree = "<group>"; };
FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; }; FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportView.swift; sourceTree = "<group>"; };
FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftParser.swift; sourceTree = "<group>"; };
FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSourceRankDateView.swift; sourceTree = "<group>"; };
FF0EC5232BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-07-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL DAMES-07-2023.csv"; sourceTree = "<group>"; };
FF0EC5242BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-08-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL DAMES-08-2023.csv"; sourceTree = "<group>"; };
FF0EC5252BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-07-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_1-07-2023.csv"; sourceTree = "<group>"; };
FF0EC5262BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-08-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_1-08-2023.csv"; sourceTree = "<group>"; };
FF0EC5272BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-07-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_2-07-2023.csv"; sourceTree = "<group>"; };
FF0EC5282BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-08-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_2-08-2023.csv"; sourceTree = "<group>"; };
FF0EC5292BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-07-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_3-07-2023.csv"; sourceTree = "<group>"; };
FF0EC52A2BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-08-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT PADEL MESSIEURS_3-08-2023.csv"; sourceTree = "<group>"; };
FF0EC52B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-01-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-01-2023.csv"; sourceTree = "<group>"; };
FF0EC52C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-02-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-02-2023.csv"; sourceTree = "<group>"; };
FF0EC52D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-03-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-03-2023.csv"; sourceTree = "<group>"; };
FF0EC52E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-04-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-04-2023.csv"; sourceTree = "<group>"; };
FF0EC52F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-05-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-05-2023.csv"; sourceTree = "<group>"; };
FF0EC5302BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-06-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-06-2023.csv"; sourceTree = "<group>"; };
FF0EC5312BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-08-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-08-2022.csv"; sourceTree = "<group>"; };
FF0EC5322BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-09-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-09-2022.csv"; sourceTree = "<group>"; };
FF0EC5332BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-10-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-10-2022.csv"; sourceTree = "<group>"; };
FF0EC5342BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-11-2022.csv"; sourceTree = "<group>"; };
FF0EC5352BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-DAMES-12-2022.csv"; sourceTree = "<group>"; };
FF0EC5362BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-01-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-01-2023.csv"; sourceTree = "<group>"; };
FF0EC5372BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-01-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-01-2023.csv"; sourceTree = "<group>"; };
FF0EC5382BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv"; sourceTree = "<group>"; };
FF0EC5392BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv"; sourceTree = "<group>"; };
FF0EC53A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv"; sourceTree = "<group>"; };
FF0EC53B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv"; sourceTree = "<group>"; };
FF0EC53C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv"; sourceTree = "<group>"; };
FF0EC53D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv"; sourceTree = "<group>"; };
FF0EC53E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv"; sourceTree = "<group>"; };
FF0EC53F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv"; sourceTree = "<group>"; };
FF0EC5402BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv"; sourceTree = "<group>"; };
FF0EC5412BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-02-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-02-2023.csv"; sourceTree = "<group>"; };
FF0EC5422BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv"; sourceTree = "<group>"; };
FF0EC5432BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv"; sourceTree = "<group>"; };
FF0EC5442BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-03-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-03-2023.csv"; sourceTree = "<group>"; };
FF0EC5452BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-04-2023.csv"; sourceTree = "<group>"; };
FF0EC5462BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-05-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-05-2023.csv"; sourceTree = "<group>"; };
FF0EC5472BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-06-2023.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-06-2023.csv"; sourceTree = "<group>"; };
FF0EC5482BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-08-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-08-2022.csv"; sourceTree = "<group>"; };
FF0EC5492BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-09-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-09-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>"; };
FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; 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>"; };
@ -266,6 +357,9 @@
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>"; };
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>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; };
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
@ -277,7 +371,6 @@
FFD784032B91C280000F62A6 /* EmptyActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyActivityView.swift; sourceTree = "<group>"; }; FFD784032B91C280000F62A6 /* EmptyActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyActivityView.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalPlayer.swift; sourceTree = "<group>"; }; FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalPlayer.swift; sourceTree = "<group>"; };
FFF8ACD12B9238C3008466FA /* FileImportManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFF8ACD32B92392C008466FA /* SourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFileManager.swift; sourceTree = "<group>"; }; FFF8ACD32B92392C008466FA /* SourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFileManager.swift; sourceTree = "<group>"; };
FFF8ACD52B923960008466FA /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; }; FFF8ACD52B923960008466FA /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
FFF8ACD82B923F3C008466FA /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; }; FFF8ACD82B923F3C008466FA /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
@ -334,6 +427,7 @@
C425D3FF2B6D249D002A7B48 /* PadelClub */ = { C425D3FF2B6D249D002A7B48 /* PadelClub */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */,
C425D44E2B6D24E1002A7B48 /* LeStorage.xcodeproj */, C425D44E2B6D24E1002A7B48 /* LeStorage.xcodeproj */,
C425D4002B6D249D002A7B48 /* PadelClubApp.swift */, C425D4002B6D249D002A7B48 /* PadelClubApp.swift */,
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */,
@ -343,6 +437,7 @@
FFF8ACD02B9238A2008466FA /* Manager */, FFF8ACD02B9238A2008466FA /* Manager */,
FFF8ACD72B923F26008466FA /* Extensions */, FFF8ACD72B923F26008466FA /* Extensions */,
C425D4042B6D249E002A7B48 /* Assets.xcassets */, C425D4042B6D249E002A7B48 /* Assets.xcassets */,
FF0EC54D2BB195CA0056B6D1 /* CSV */,
C425D4062B6D249E002A7B48 /* Preview Content */, C425D4062B6D249E002A7B48 /* Preview Content */,
); );
path = PadelClub; path = PadelClub;
@ -494,6 +589,55 @@
path = Player; path = Player;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FF0EC54D2BB195CA0056B6D1 /* CSV */ = {
isa = PBXGroup;
children = (
FF0EC5232BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-07-2023.csv */,
FF0EC5242BB195CA0056B6D1 /* CLASSEMENT PADEL DAMES-08-2023.csv */,
FF0EC5252BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-07-2023.csv */,
FF0EC5262BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-08-2023.csv */,
FF0EC5272BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-07-2023.csv */,
FF0EC5282BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-08-2023.csv */,
FF0EC5292BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-07-2023.csv */,
FF0EC52A2BB195CA0056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-08-2023.csv */,
FF0EC52B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-01-2023.csv */,
FF0EC52C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-02-2023.csv */,
FF0EC52D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-03-2023.csv */,
FF0EC52E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-04-2023.csv */,
FF0EC52F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-05-2023.csv */,
FF0EC5302BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-06-2023.csv */,
FF0EC5312BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-08-2022.csv */,
FF0EC5322BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-09-2022.csv */,
FF0EC5332BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-10-2022.csv */,
FF0EC5342BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv */,
FF0EC5352BB195CA0056B6D1 /* CLASSEMENT-PADEL-DAMES-12-2022.csv */,
FF0EC5362BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-01-2023.csv */,
FF0EC5372BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-01-2023.csv */,
FF0EC5382BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv */,
FF0EC5392BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv */,
FF0EC53A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv */,
FF0EC53B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv */,
FF0EC53C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv */,
FF0EC53D2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv */,
FF0EC53E2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv */,
FF0EC53F2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv */,
FF0EC5402BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv */,
FF0EC5412BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-02-2023.csv */,
FF0EC5422BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv */,
FF0EC5432BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv */,
FF0EC5442BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-03-2023.csv */,
FF0EC5452BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv */,
FF0EC5462BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-05-2023.csv */,
FF0EC5472BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-06-2023.csv */,
FF0EC5482BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-08-2022.csv */,
FF0EC5492BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-09-2022.csv */,
FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */,
FF0EC54B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv */,
FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */,
);
path = CSV;
sourceTree = "<group>";
};
FF1DC54D2BAB34FA00FD8220 /* Club */ = { FF1DC54D2BAB34FA00FD8220 /* Club */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -525,6 +669,7 @@
FF70916B2B91005400AB08DA /* TournamentView.swift */, FF70916B2B91005400AB08DA /* TournamentView.swift */,
FF8F26402BADFC8700650388 /* TournamentInitView.swift */, FF8F26402BADFC8700650388 /* TournamentInitView.swift */,
FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */, FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */,
FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */,
FF3F74F92B91A018004CFE0E /* Screen */, FF3F74F92B91A018004CFE0E /* Screen */,
FF3F74F82B919FB2004CFE0E /* Shared */, FF3F74F82B919FB2004CFE0E /* Shared */,
); );
@ -645,6 +790,7 @@
FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */, FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */,
FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */, FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */,
FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */, FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */,
FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -705,11 +851,13 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF1DC5582BAB767000FD8220 /* Tips.swift */, FF1DC5582BAB767000FD8220 /* Tips.swift */,
FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */,
FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */, FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */,
FFF8ACD12B9238C3008466FA /* FileImportManager.swift */,
FFF8ACD32B92392C008466FA /* SourceFileManager.swift */, FFF8ACD32B92392C008466FA /* SourceFileManager.swift */,
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */, FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */,
FF8F26352BAD523300650388 /* PadelRule.swift */, FF8F26352BAD523300650388 /* PadelRule.swift */,
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */,
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */,
FF6EC9072B947A1E00EA7F5A /* Network */, FF6EC9072B947A1E00EA7F5A /* Network */,
); );
path = Manager; path = Manager;
@ -857,6 +1005,48 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */, C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */,
FF0EC54E2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-02-2023.csv in Resources */,
FF0EC54F2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-08-2022.csv in Resources */,
FF0EC5502BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-12-2022.csv in Resources */,
FF0EC5512BB195E20056B6D1 /* CLASSEMENT PADEL DAMES-07-2023.csv in Resources */,
FF0EC5522BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-02-2023.csv in Resources */,
FF0EC5532BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-09-2022.csv in Resources */,
FF0EC5542BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-04-2023.csv in Resources */,
FF0EC5552BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-08-2023.csv in Resources */,
FF0EC5562BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-10-2022.csv in Resources */,
FF0EC5572BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-10-2022.csv in Resources */,
FF0EC5582BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-05-2023.csv in Resources */,
FF0EC5592BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-03-2023.csv in Resources */,
FF0EC55A2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_2-07-2023.csv in Resources */,
FF0EC55B2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-01-2023.csv in Resources */,
FF0EC55C2BB195E20056B6D1 /* CLASSEMENT PADEL DAMES-08-2023.csv in Resources */,
FF0EC55D2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-04-2023.csv in Resources */,
FF0EC55E2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-05-2023.csv in Resources */,
FF0EC55F2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-03-2023.csv in Resources */,
FF0EC5602BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-08-2023.csv in Resources */,
FF0EC5612BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-05-2023.csv in Resources */,
FF0EC5622BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-05-2023.csv in Resources */,
FF0EC5632BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-06-2023.csv in Resources */,
FF0EC5642BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-11-2022.csv in Resources */,
FF0EC5652BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-12-2022.csv in Resources */,
FF0EC5662BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-02-2023.csv in Resources */,
FF0EC5672BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-03-2023.csv in Resources */,
FF0EC5682BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-06-2023.csv in Resources */,
FF0EC5692BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv in Resources */,
FF0EC56A2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv in Resources */,
FF0EC56B2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-06-2023.csv in Resources */,
FF0EC56C2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-07-2023.csv in Resources */,
FF0EC56D2BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-3-06-2023.csv in Resources */,
FF0EC56E2BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_1-08-2023.csv in Resources */,
FF0EC56F2BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-09-2022.csv in Resources */,
FF0EC5702BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-08-2022.csv in Resources */,
FF0EC5712BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-01-2023.csv in Resources */,
FF0EC5722BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-09-2022.csv in Resources */,
FF0EC5732BB195E20056B6D1 /* CLASSEMENT PADEL MESSIEURS_3-07-2023.csv in Resources */,
FF0EC5742BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-2-01-2023.csv in Resources */,
FF0EC5752BB195E20056B6D1 /* CLASSEMENT-PADEL-DAMES-11-2022.csv in Resources */,
FF0EC5762BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv in Resources */,
FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */,
C425D4052B6D249E002A7B48 /* Assets.xcassets in Resources */, C425D4052B6D249E002A7B48 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -885,6 +1075,7 @@
C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */, C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */,
FF7091662B90F0B000AB08DA /* TabDestination.swift in Sources */, FF7091662B90F0B000AB08DA /* TabDestination.swift in Sources */,
FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */,
FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */,
C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */,
FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */,
C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */,
@ -899,6 +1090,7 @@
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */, FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */,
FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */, FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */,
FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */,
C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */,
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */,
FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */,
@ -939,6 +1131,7 @@
FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */, FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */,
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */, FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */,
FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */,
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */,
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */, FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.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 */,
@ -948,6 +1141,7 @@
FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */, FF82CFC92B9132AF00B0CAF2 /* ActivityView.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 */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */, C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */, FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
@ -960,7 +1154,6 @@
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */, C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */,
C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */,
FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */,
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */, FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */,
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF089EB82BB00ABF00F0AEC7 /* InscriptionTipsView.swift in Sources */, FF089EB82BB00ABF00F0AEC7 /* InscriptionTipsView.swift in Sources */,
@ -986,6 +1179,7 @@
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */, FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */, FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */,
FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */, FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */,
FF0EC5222BB173E70056B6D1 /* UpdateSourceRankDateView.swift in Sources */,
C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */, C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -1153,6 +1347,7 @@
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -1183,6 +1378,7 @@
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;

@ -43,15 +43,15 @@ class DataStore: ObservableObject {
// store.addMigration(Migration<TournamentV1, TournamentV2>(version: 2)) // store.addMigration(Migration<TournamentV1, TournamentV2>(version: 2))
// store.addMigration(Migration<TournamentV2, Tournament>(version: 3)) // store.addMigration(Migration<TournamentV2, Tournament>(version: 3))
self.clubs = store.registerCollection(synchronized: false) self.clubs = store.registerCollection(synchronized: false, indexed: true)
self.tournaments = store.registerCollection(synchronized: false) self.tournaments = store.registerCollection(synchronized: false, indexed: true)
self.events = store.registerCollection(synchronized: false) self.events = store.registerCollection(synchronized: false, indexed: true)
self.groupStages = store.registerCollection(synchronized: false) self.groupStages = store.registerCollection(synchronized: false, indexed: true)
self.teamScores = store.registerCollection(synchronized: false) self.teamScores = store.registerCollection(synchronized: false, indexed: true)
self.teamRegistrations = store.registerCollection(synchronized: false) self.teamRegistrations = store.registerCollection(synchronized: false, indexed: true)
self.playerRegistrations = store.registerCollection(synchronized: false) self.playerRegistrations = store.registerCollection(synchronized: false, indexed: true)
self.rounds = store.registerCollection(synchronized: false) self.rounds = store.registerCollection(synchronized: false, indexed: true)
self.matches = store.registerCollection(synchronized: false) self.matches = store.registerCollection(synchronized: false, indexed: true)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil)

@ -36,6 +36,10 @@ class GroupStage: ModelObject, Storable {
self.startDate = startDate self.startDate = startDate
} }
func teamsAt(_ index: Int) -> TeamRegistration? {
teams().first(where: { $0.groupStagePosition == index })
}
func tournamentObject() -> Tournament? { func tournamentObject() -> Tournament? {
Store.main.findById(tournament) Store.main.findById(tournament)
} }
@ -75,7 +79,7 @@ class GroupStage: ModelObject, Storable {
_matches.append(newMatch) _matches.append(newMatch)
} }
try? DataStore.shared.matches.append(contentOfs: _matches) try? DataStore.shared.matches.addOrUpdate(contentOfs: _matches)
} }
func removeMatches() { func removeMatches() {
@ -90,7 +94,7 @@ class GroupStage: ModelObject, Storable {
Store.main.filter { $0.groupStage == self.id } Store.main.filter { $0.groupStage == self.id }
} }
var teams: [TeamRegistration] { func teams() -> [TeamRegistration] {
Store.main.filter { $0.groupStage == self.id } Store.main.filter { $0.groupStage == self.id }
} }

@ -23,7 +23,20 @@ extension Tournament {
} }
static func newEmptyInstance() -> Tournament { static func newEmptyInstance() -> Tournament {
Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) let lastDataSource: String? = UserDefaults.standard.string(forKey: "lastDataSource")
let lastDataSourceMaleUnranked: Int = UserDefaults.standard.integer(forKey: "lastDataSourceMaleUnranked")
let lastDataSourceFemaleUnranked: Int = UserDefaults.standard.integer(forKey: "lastDataSourceFemaleUnranked")
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let maleUnrankedValue : Int? = lastDataSourceMaleUnranked == 0 ? nil : lastDataSourceMaleUnranked
let femaleUnrankedValue : Int? = lastDataSourceFemaleUnranked == 0 ? nil : lastDataSourceMaleUnranked
let rankSourceDate = _mostRecentDateAvailable
return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior, maleUnrankedValue: maleUnrankedValue, femaleUnrankedValue: femaleUnrankedValue)
} }
} }

@ -28,10 +28,11 @@ class PlayerRegistration: ModelObject, Storable {
var ligueName: String? var ligueName: String?
var assimilation: String? var assimilation: String?
// var phoneNumber: String? var phoneNumber: String?
// var email: String? var email: String?
// var birthDate: Date? var birthdate: String?
// var club: String?
var weight: Int = 0
internal init(teamRegistration: String = "", firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, registrationType: Int? = nil, registrationDate: Date? = nil, sex: Int) { internal init(teamRegistration: String = "", firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, registrationType: Int? = nil, registrationDate: Date? = nil, sex: Int) {
self.teamRegistration = teamRegistration self.teamRegistration = teamRegistration
@ -58,6 +59,39 @@ class PlayerRegistration: ModelObject, Storable {
self.assimilation = importedPlayer.assimilation self.assimilation = importedPlayer.assimilation
} }
internal init(federalData: [String], sex: Int, sexUnknown: Bool) {
lastName = federalData[0]
firstName = federalData[1]
birthdate = federalData[2]
licenceId = federalData[3]
clubName = federalData[4]
rank = Int(federalData[5])
email = federalData[6]
phoneNumber = federalData[7]
// manuallyCreated = false
if sexUnknown {
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {
self.sex = 0
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) {
self.sex = 1
} else {
self.sex = -1
}
} else {
self.sex = sex
}
}
func contains(_ searchField: String) -> Bool {
firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField)
}
func isSameAs(_ player: PlayerRegistration) -> Bool {
firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame &&
lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame
}
func tournament() -> Tournament? { func tournament() -> Tournament? {
guard let tournament = team()?.tournament else { return nil } guard let tournament = team()?.tournament else { return nil }
return Store.main.findById(tournament) return Store.main.findById(tournament)
@ -87,22 +121,78 @@ class PlayerRegistration: ModelObject, Storable {
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 { if let rank, rank > 0 {
return rank.ordinalFormatted() return rank.formatted()
} else { } else {
return "non classé" + (isMalePlayer() ? "" : "e") return "non classé" + (isMalePlayer() ? "" : "e")
} }
} }
var computedRank: Int { func getRank() -> Int {
rank ?? tournament()?.unrankValue(for: isMalePlayer()) ?? Int.max weight
}
@MainActor
func updateRank(from sources: [CSVParser], lastRank: Int) async throws {
if let value = try await history(from: sources)?.rank {
rank = value
} else {
rank = lastRank
}
} }
func rank(for tournamentCategory: TournamentCategory, manMax: Int, womanMax: Int) -> Int { func history(from sources: [CSVParser]) async throws -> Line? {
switch tournamentCategory { guard let license = licenceId?.strippedLicense else {
return try await historyFromName(from: sources)
}
return await withTaskGroup(of: Line?.self) { group in
for source in sources.filter({ $0.maleData == isMalePlayer() }) {
group.addTask {
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first(where: { line in
line.rawValue.contains(";\(license);")
})
}
}
if let first = await group.first(where: { $0 != nil }) {
group.cancelAll()
return first
} else {
return nil
}
}
}
func historyFromName(from sources: [CSVParser]) async throws -> Line? {
return await withTaskGroup(of: Line?.self) { group in
for source in sources.filter({ $0.maleData == isMalePlayer() }) {
group.addTask { [lastName, firstName] in
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first(where: { line in
line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);")
})
}
}
if let first = await group.first(where: { $0 != nil }) {
group.cancelAll()
return first
} else {
return nil
}
}
}
func setWeight(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000
switch tournament.tournamentCategory {
case .men: case .men:
return isMalePlayer() ? computedRank : computedRank + PlayerRegistration.addon(for: computedRank, manMax: manMax, womanMax: womanMax) weight = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
case .women, .mix: default:
return computedRank weight = currentRank
} }
} }
@ -125,6 +215,11 @@ class PlayerRegistration: ModelObject, Storable {
case _clubName = "clubName" case _clubName = "clubName"
case _ligueName = "ligueName" case _ligueName = "ligueName"
case _assimilation = "assimilation" case _assimilation = "assimilation"
case _birthdate = "birthdate"
case _phoneNumber = "phoneNumber"
case _email = "email"
case _weight = "weight"
} }
enum PaymentType: Int, CaseIterable, Identifiable { enum PaymentType: Int, CaseIterable, Identifiable {

@ -25,7 +25,13 @@ class TeamRegistration: ModelObject, Storable {
var logo: String? var logo: String?
var name: String? var name: String?
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) { var walkOut: Bool = false
var wildCardBracket: Bool = false
var wildCardGroupStage: Bool = false
var category: Int?
var weight: Int = 0
internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) {
self.tournament = tournament self.tournament = tournament
self.groupStage = groupStage self.groupStage = groupStage
self.registrationDate = registrationDate self.registrationDate = registrationDate
@ -37,6 +43,44 @@ class TeamRegistration: ModelObject, Storable {
self.sourceValue = sourceValue self.sourceValue = sourceValue
self.logo = logo self.logo = logo
self.name = name self.name = name
self.category = category
}
var tournamentCategory: TournamentCategory {
get {
TournamentCategory(rawValue: category ?? 0) ?? .men
}
set {
category = newValue.rawValue
}
}
func teamLabel(_ displayStyle: DisplayStyle = .wide) -> String {
unsortedPlayers().map { $0.playerLabel(displayStyle) }.joined(separator: " & ")
}
func formattedSeed(in teams: [TeamRegistration]) -> String {
if let index = teams.firstIndex(where: { $0.id == id }) {
return "#\(index + 1)"
} else {
return "###"
}
}
func contains(_ searchField: String) -> Bool {
unsortedPlayers().anySatisfy({ $0.contains(searchField) }) || self.name?.localizedCaseInsensitiveContains(searchField) == true
}
func includes(_ players: [PlayerRegistration]) -> Bool {
players.allSatisfy { player in
includes(player)
}
}
func includes(_ player: PlayerRegistration) -> Bool {
unsortedPlayers().anySatisfy { _player in
_player.isSameAs(player)
}
} }
var computedPosition: Int { var computedPosition: Int {
@ -44,12 +88,14 @@ class TeamRegistration: ModelObject, Storable {
} }
func updatePlayers(_ players: Set<PlayerRegistration>) { func updatePlayers(_ players: Set<PlayerRegistration>) {
self.players().forEach { player in self.unsortedPlayers().forEach { player in
if players.contains(player) == false { if players.contains(player) == false {
try? DataStore.shared.playerRegistrations.delete(instance: player) try? DataStore.shared.playerRegistrations.delete(instance: player)
} }
} }
setWeight(from: Array(players))
players.forEach { player in players.forEach { player in
player.teamRegistration = id player.teamRegistration = id
try? DataStore.shared.playerRegistrations.addOrUpdate(instance: player) try? DataStore.shared.playerRegistrations.addOrUpdate(instance: player)
@ -68,7 +114,7 @@ class TeamRegistration: ModelObject, Storable {
Store.main.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in Store.main.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in
let predicates: [AreInIncreasingOrder] = [ let predicates: [AreInIncreasingOrder] = [
{ $0.sex < $1.sex }, { $0.sex < $1.sex },
{ $0.computedRank < $1.computedRank }, { $0.rank ?? 0 < $1.rank ?? 0 },
{ $0.lastName < $1.lastName}, { $0.lastName < $1.lastName},
{ $0.firstName < $1.firstName } { $0.firstName < $1.firstName }
] ]
@ -85,8 +131,13 @@ class TeamRegistration: ModelObject, Storable {
} }
} }
func computedRank() -> Int { func unsortedPlayers() -> [PlayerRegistration] {
(players().prefix(significantPlayerCount()).map { $0.computedRank } + missing().map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount()).reduce(0,+) Store.main.filter { $0.teamRegistration == self.id }
}
func setWeight(from players: [PlayerRegistration]) {
let significantPlayerCount = significantPlayerCount()
weight = (players.prefix(significantPlayerCount).map { $0.weight } + missingPlayerType().map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
} }
func significantPlayerCount() -> Int { func significantPlayerCount() -> Int {
@ -94,7 +145,6 @@ class TeamRegistration: ModelObject, Storable {
} }
func mandatoryPlayerType() -> [Int] { func mandatoryPlayerType() -> [Int] {
guard let tournamentCategory = tournamentObject()?.tournamentCategory else { return [] }
switch tournamentCategory { switch tournamentCategory {
case .mix: case .mix:
return [0, 1] return [0, 1]
@ -105,8 +155,10 @@ class TeamRegistration: ModelObject, Storable {
} }
} }
func missing() -> [Int] { func missingPlayerType() -> [Int] {
let s = players().map { $0.sex } let players = unsortedPlayers()
if players.count < 2 { return [] }
let s = players.map { $0.sex }
var missing = mandatoryPlayerType() var missing = mandatoryPlayerType()
s.forEach { i in s.forEach { i in
if let index = missing.firstIndex(of: i) { if let index = missing.firstIndex(of: i) {
@ -117,7 +169,7 @@ class TeamRegistration: ModelObject, Storable {
} }
func unrankValue(for malePlayer: Bool) -> Int { func unrankValue(for malePlayer: Bool) -> Int {
tournamentObject()?.unrankValue(for: malePlayer) ?? Int.max tournamentObject()?.unrankValue(for: malePlayer) ?? 100_000
} }
func tournamentObject() -> Tournament? { func tournamentObject() -> Tournament? {
@ -125,7 +177,7 @@ class TeamRegistration: ModelObject, Storable {
} }
override func deleteDependencies() throws { override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.players()) try Store.main.deleteDependencies(items: self.unsortedPlayers())
} }
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
@ -141,5 +193,10 @@ class TeamRegistration: ModelObject, Storable {
case _sourceValue = "sourceValue" case _sourceValue = "sourceValue"
case _logo = "logo" case _logo = "logo"
case _name = "name" case _name = "name"
case _wildCardBracket = "wildCardBracket"
case _wildCardGroupStage = "wildCardGroupStage"
case _category = "category"
case _weight = "weight"
case _walkOut = "walkOut"
} }
} }

@ -99,22 +99,54 @@ class Tournament : ModelObject, Storable {
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) Store.main.filter { $0.tournament == self.id }.sorted(by: \.index)
} }
func unsortedTeams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }
}
typealias TeamRegistrationCompare = (TeamRegistration, TeamRegistration) -> Bool
func teams() -> [TeamRegistration] { func teams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }.sorted { Store.main.filter { $0.tournament == self.id }
if $0.computedRank() == $1.computedRank() { .sorted { (lhs, rhs) in
return $0.registrationDate ?? .distantPast < $1.registrationDate ?? .distantPast let predicates: [TeamRegistrationCompare] = [
} else { { $0.weight < $1.weight },
return $0.computedRank() < $1.computedRank() { $0.registrationDate ?? .distantPast < $1.registrationDate ?? .distantPast },
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}
}
func duplicates() -> [PlayerRegistration] {
var duplicates = [PlayerRegistration]()
let players = unsortedPlayers()
Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
let found = players.filter({ $0.licenceId == licenceId })
if found.count > 1 {
duplicates.append(found.first!)
} }
} }
return duplicates
}
func unsortedPlayers() -> [PlayerRegistration] {
unsortedTeams().flatMap { $0.unsortedPlayers() }
} }
func players() -> [PlayerRegistration] { func players() -> [PlayerRegistration] {
teams().flatMap { $0.players() } unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight)
} }
func femalePlayers() -> [PlayerRegistration] { func femalePlayers() -> [PlayerRegistration] {
players().filter({ $0.isMalePlayer() == false }) unsortedPlayers().filter({ $0.isMalePlayer() == false })
} }
func unrankValue(for malePlayer: Bool) -> Int? { func unrankValue(for malePlayer: Bool) -> Int? {
@ -136,10 +168,60 @@ class Tournament : ModelObject, Storable {
2 2
} }
func importTeams(_ teams: [FileImportManager.TeamHolder], keepPreviousData: Bool = false) {
teams.forEach { team in
addTeam(Set([team.playerOne, team.playerTwo]))
}
}
func updateWeights() {
let teams = self.unsortedTeams()
teams.forEach { team in
let players = team.unsortedPlayers()
players.forEach { $0.setWeight(in: self) }
team.setWeight(from: players)
try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
}
try? DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teams)
}
func updateRank(to newDate: Date?) async throws {
guard let newDate else { return }
rankSourceDate = newDate
let maleDataURLs = SourceFile.allFiles(true).filter { $0.dateFromPath == newDate }
let femaleDataURLs = SourceFile.allFiles(false).filter { $0.dateFromPath == newDate }
let lastRankWoman = femaleDataURLs.compactMap { $0.getUnrankedValue() }.first
let lastRankMan = maleDataURLs.compactMap { $0.getUnrankedValue() }.sorted().last
let dataURLs = maleDataURLs + femaleDataURLs
let sources = dataURLs.map { CSVParser(url: $0) }
await unsortedPlayers().concurrentForEach { player in
try? await player.updateRank(from: sources, lastRank: (player.sex == 0 ? lastRankWoman : lastRankMan) ?? 0)
}
await MainActor.run {
self.maleUnrankedValue = lastRankMan
self.femaleUnrankedValue = lastRankWoman
// if inscriptionClosed == false {
// orderedEntries.forEach { entrant in
// entrant.initialRank = entrant.updatedRank
// }
// }
}
}
func missingUnrankedValue() -> Bool { func missingUnrankedValue() -> Bool {
maleUnrankedValue == nil || femaleUnrankedValue == nil maleUnrankedValue == nil || femaleUnrankedValue == nil
} }
func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? {
unsortedTeams().first(where: { $0.includes(players) })
}
func title(_ displayStyle: DisplayStyle = .wide) -> String { func title(_ displayStyle: DisplayStyle = .wide) -> String {
[tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ") [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ")
} }
@ -163,7 +245,7 @@ class Tournament : ModelObject, Storable {
func qualifiedTeams() -> [TeamRegistration] { func qualifiedTeams() -> [TeamRegistration] {
teams().filter({ $0.qualified() }) unsortedTeams().filter({ $0.qualified() })
} }
func moreQualifiedToDraw() -> Int { func moreQualifiedToDraw() -> Int {
@ -173,7 +255,7 @@ class Tournament : ModelObject, Storable {
func missingQualifiedFromGroupStages() -> [TeamRegistration] { func missingQualifiedFromGroupStages() -> [TeamRegistration] {
if groupStageAdditionalQualified > 0 { if groupStageAdditionalQualified > 0 {
return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in
groupStage.teams[qualifiedPerGroupStage] groupStage.teams()[qualifiedPerGroupStage]
} }
.filter({ $0.qualified() == false }) .filter({ $0.qualified() == false })
} else { } else {
@ -228,10 +310,10 @@ class Tournament : ModelObject, Storable {
_groupStages.append(groupStage) _groupStages.append(groupStage)
} }
try? DataStore.shared.groupStages.append(contentOfs: _groupStages) try? DataStore.shared.groupStages.addOrUpdate(contentOfs: _groupStages)
groupStages().forEach { $0.buildMatches() } groupStages().forEach { $0.buildMatches() }
refreshBrackets() refreshGroupStages()
} }
func resetStructure() { func resetStructure() {
@ -242,13 +324,12 @@ class Tournament : ModelObject, Storable {
} }
func refreshBrackets() { func refreshGroupStages() {
// completeEntries.forEach { entrant in unsortedTeams().forEach { team in
// if entrant.bracketPosition > 0 { team.groupStage = nil
// entrant.resetBracketPosition() team.groupStagePosition = nil
// } }
// }
//
if groupStageCount > 0 { if groupStageCount > 0 {
switch groupStageOrderingMode { switch groupStageOrderingMode {
case .random: case .random:
@ -262,28 +343,31 @@ class Tournament : ModelObject, Storable {
} }
func setBrackets(randomize: Bool) { func setBrackets(randomize: Bool) {
let numberOfBracketsAsInt = groupStages().count let groupStages = groupStages()
let numberOfBracketsAsInt = groupStages.count
// let teamsPerBracket = Int(teamsPerBracket) // let teamsPerBracket = Int(teamsPerBracket)
if groupStageCount != numberOfBracketsAsInt { if groupStageCount != numberOfBracketsAsInt {
buildGroupStages() buildGroupStages()
return return
} }
let max = groupStages().map { $0.size }.reduce(0,+) let max = groupStages.map { $0.size }.reduce(0,+)
// var chunks = orderedEntries.filter { $0.wcFinalTable == false }.suffix(Int(max)).chunked(into: numberOfBracketsAsInt) var chunks = teams().filter { $0.wildCardBracket == false }.suffix(max).chunked(into: numberOfBracketsAsInt)
// for (index, _) in chunks.enumerated() { for (index, _) in chunks.enumerated() {
// if randomize { if randomize {
// chunks[index].shuffle() chunks[index].shuffle()
// } else if index % 2 != 0 { } else if index % 2 != 0 {
// chunks[index].reverse() chunks[index].reverse()
// } }
//
// print("Equipes \(chunks[index].map { $0.initialRank })") print("Equipes \(chunks[index].map { $0.weight })")
// for (jIndex, _) in chunks[index].enumerated() { for (jIndex, _) in chunks[index].enumerated() {
// print("Position \(index+1) Poule \(orderedBrackets[jIndex].index)") print("Position \(index+1) Poule \(groupStages[jIndex].index)")
// chunks[index][jIndex].bracketPosition = orderedBrackets[jIndex].index chunks[index][jIndex].groupStage = groupStages[jIndex].id
// chunks[index][jIndex].bracketPositions = [Int(index + 1)] chunks[index][jIndex].groupStagePosition = index
// }
// } try? DataStore.shared.teamRegistrations.addOrUpdate(instance: chunks[index][jIndex])
}
}
} }
@ -293,11 +377,13 @@ class Tournament : ModelObject, Storable {
func addTeam(_ players: Set<PlayerRegistration>) { func addTeam(_ players: Set<PlayerRegistration>) {
let team = TeamRegistration(tournament: id, registrationDate: Date()) let team = TeamRegistration(tournament: id, registrationDate: Date())
team.tournamentCategory = tournamentCategory
team.setWeight(from: Array(players))
try? DataStore.shared.teamRegistrations.addOrUpdate(instance: team) try? DataStore.shared.teamRegistrations.addOrUpdate(instance: team)
players.forEach { player in players.forEach { player in
player.teamRegistration = team.id player.teamRegistration = team.id
} }
try? DataStore.shared.playerRegistrations.append(contentOfs: players) try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
} }
var teamSortingType: TeamSortingType { var teamSortingType: TeamSortingType {
@ -350,7 +436,12 @@ class Tournament : ModelObject, Storable {
TournamentCategory(rawValue: federalCategory) ?? .men TournamentCategory(rawValue: federalCategory) ?? .men
} }
set { set {
federalCategory = newValue.rawValue if federalCategory != newValue.rawValue {
federalCategory = newValue.rawValue
updateWeights()
} else {
federalCategory = newValue.rawValue
}
} }
} }
@ -405,6 +496,12 @@ class Tournament : ModelObject, Storable {
return matchFormat return matchFormat
} }
} }
override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.unsortedTeams())
try Store.main.deleteDependencies(items: self.groupStages())
//try Store.main.deleteDependencies(items: self.rounds())
}
} }
extension Tournament { extension Tournament {

@ -20,3 +20,20 @@ extension Sequence {
AnySequence(zip(self, self.dropFirst())) AnySequence(zip(self, self.dropFirst()))
} }
} }
extension Sequence {
func concurrentForEach(
_ operation: @escaping (Element) async -> Void
) async {
// 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
for element in self {
group.addTask {
await operation(element)
}
}
}
}
}

@ -19,6 +19,10 @@ extension String {
var canonicalVersion: String { var canonicalVersion: String {
trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased() trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased()
} }
var canonicalVersionWithPunctuation: String {
trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
} }
extension String { extension String {

@ -49,3 +49,42 @@ extension URL {
Bundle.main.url(forResource: "SeedData", withExtension: nil) Bundle.main.url(forResource: "SeedData", withExtension: nil)
} }
} }
extension URL {
func getUnrankedValue() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
// Get the last non-empty line
var lastLine: String?
for line in lines.reversed() {
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedLine.isEmpty {
lastLine = trimmedLine
break
}
}
guard let rankString = lastLine?.components(separatedBy: ";").dropFirst().first, let rank = Int(rankString) else {
return nil
}
// Define the regular expression pattern
let pattern = "\\b\(NSRegularExpression.escapedPattern(for: rankString))\\b"
// Create the regular expression object
guard let regex = try? NSRegularExpression(pattern: pattern) else {
return nil
}
// Get the matches
let matches = regex.matches(in: fileContents, range: NSRange(fileContents.startIndex..., in: fileContents))
// Return the count of matches
return matches.count + rank - 1
}
}

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeName</key>
<string>CSV,XLS</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.content</string>
<string>public.data</string>
</array>
</dict>
</array>
</dict>
</plist>

@ -10,6 +10,248 @@ import Foundation
class FileImportManager { class FileImportManager {
static let shared = FileImportManager() static let shared = FileImportManager()
func foundInWomenData(license: String?) -> Bool {
guard let license = license?.strippedLicense else {
return false
}
do {
return try SourceFile.allFilesSortedByDate(false).first(where: {
let fileContent = try String(contentsOf: $0)
return fileContent.contains(";\(license);")
}) != nil
} catch {
print("history", error)
return false
}
}
func foundInMenData(license: String?) -> Bool {
guard let license = license?.strippedLicense else {
return false
}
do {
return try SourceFile.allFilesSortedByDate(true).first(where: {
let fileContent = try String(contentsOf: $0)
return fileContent.contains(";\(license);")
}) != nil
} catch {
print("history", error)
return false
}
}
enum FileProvider: CaseIterable, Identifiable {
var id: Self { self }
case frenchFederation
case unknown
var localizedLabel: String {
switch self {
case .frenchFederation:
return "FFT"
case .unknown:
return "Inconnu"
}
}
}
struct TeamHolder: Identifiable {
let id: UUID = UUID()
let playerOne: PlayerRegistration
let playerTwo: PlayerRegistration
let weight: Int
let tournamentCategory: TournamentCategory
let previousTeam: TeamRegistration?
init(playerOne: PlayerRegistration, playerTwo: PlayerRegistration, tournamentCategory: TournamentCategory, previousTeam: TeamRegistration?) {
self.playerOne = playerOne
self.playerTwo = playerTwo
self.tournamentCategory = tournamentCategory
self.previousTeam = previousTeam
self.weight = playerOne.weight + playerTwo.weight
}
func formattedSeed(in teams: [TeamHolder]) -> String {
if let index = teams.firstIndex(where: { $0.id == id }) {
return "#\(index + 1)"
} else {
return "###"
}
}
}
static let FFT_ASSIMILATION_WOMAN_IN_MAN = "A calculer selon la pondération en vigueur"
func createTeams(from fileContent: String, tournament: Tournament, fileProvider: FileProvider = .frenchFederation) async -> [TeamHolder] {
let lines = fileContent.components(separatedBy: "\n")
guard let firstLine = lines.first else { return [] }
var separator = ","
if firstLine.contains(";") {
separator = ";"
}
let headerCount = firstLine.components(separatedBy: separator).count
var results: [TeamHolder] = []
if headerCount == 23 && fileProvider == .unknown { //PBL
let fetchRequest = ImportedPlayer.fetchRequest()
let federalContext = PersistenceController.shared.localContainer.viewContext
lines.dropFirst().forEach { line in
let data = line.components(separatedBy: separator)
if data.count == 23 {
// let team = Team(context: context)
// let brand = Brand(context: context)
// brand.title = data[2].trimmed
// brand.qualifier = data[0].trimmed
// brand.country = data[1].trimmed
// brand.lineOfBusiness = data[3].trimmed
// if brand.lineOfBusiness == "Bâtiment / Immo" { //quick fix
// brand.lineOfBusiness = "Bâtiment / Immo / Transport"
// }
// brand.name = data[4].trimmed
// team.brand = brand
//
// for i in 0...5 {
// let sex = data[i*3+5]
// let lastName = data[i*3+6].trimmed
// let firstName = data[i*3+7].trimmed
// if lastName.isEmpty == false {
// let playerOne = Player(context: context)
// let predicate = NSPredicate(format: "(canonicalLastName matches[cd] %@ OR canonicalLastName matches[cd] %@) AND (canonicalFirstName matches[cd] %@ OR canonicalFirstName matches[cd] %@)", lastName, lastName.removePunctuationAndHyphens, firstName, firstName.removePunctuationAndHyphens)
// fetchRequest.predicate = predicate
// if let playerFound = try? federalContext.fetch(fetchRequest).first {
// playerOne.updateWithImportedPlayer(playerFound)
// } else {
// playerOne.lastName = lastName
// playerOne.firstName = firstName
// playerOne.sex = sex == "H" ? 1 : sex == "F" ? 0 : -1
// playerOne.currentRank = tournament?.lastRankMan ?? 0
// }
// team.addToPlayers(playerOne)
// }
// }
// team.category = TournamentCategory.men.importingRawValue
//
// if let players = team.players, players.count > 0 {
// results.append(team)
// } else {
// context.delete(team)
// }
}
}
return results
} else if headerCount <= 18 && fileProvider == .frenchFederation {
Array(lines.dropFirst()).chunked(into: 2).forEach { teamLines in
if teamLines.count == 2 {
let dataOne = teamLines[0].replacingOccurrences(of: "\"", with: "").components(separatedBy: separator)
var dataTwo = teamLines[1].replacingOccurrences(of: "\"", with: "").components(separatedBy: separator)
if dataOne[11] != dataTwo[3] || dataOne[12] != dataTwo[4] {
if let found = lines.map({ $0.replacingOccurrences(of: "\"", with: "").components(separatedBy: separator) }).first(where: { components in
return dataOne[11] == components[3] && dataOne[12] == components[4]
}) {
dataTwo = found
}
}
if dataOne.count == dataTwo.count {
let category = dataOne[0]
var tournamentCategory: TournamentCategory {
switch category {
case "Double Messieurs":
return .men
case "Double Dames":
return .women
case "Double Mixte":
return .mix
default:
return .men
}
}
let resultOne = Array(dataOne.dropFirst(3).dropLast())
let resultTwo = Array(dataTwo.dropFirst(3).dropLast())
let sexUnknown: Bool = (resultOne.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true) || (resultTwo.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true)
var sexPlayerOne : Int {
switch tournamentCategory {
case .men: return 1
case .women: return 0
case .mix: return 0
}
}
var sexPlayerTwo : Int {
switch tournamentCategory {
case .men: return 1
case .women: return 0
case .mix: return 1
}
}
let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
playerOne.setWeight(in: tournament)
let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
playerTwo.setWeight(in: tournament)
let team = TeamHolder(playerOne: playerOne, playerTwo: playerTwo, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team)
}
}
}
return results
} else if headerCount > 18 && fileProvider == .frenchFederation {
lines.dropFirst().forEach { line in
let data = line.components(separatedBy: separator)
if data.count > 18 {
let category = data[0]
let sexUnknown: Bool = (data.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true)
var tournamentCategory: TournamentCategory {
switch category {
case "Double Messieurs":
return .men
case "Double Dames":
return .women
case "Double Mixte":
return .mix
default:
return .men
}
}
let result = Array(data.dropFirst(3).dropLast())
var sexPlayerOne : Int {
switch tournamentCategory {
case .men: return 1
case .women: return 0
case .mix: return 1
}
}
var sexPlayerTwo : Int {
switch tournamentCategory {
case .men: return 1
case .women: return 0
case .mix: return 0
}
}
let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
playerOne.setWeight(in: tournament)
let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
playerTwo.setWeight(in: tournament)
let team = TeamHolder(playerOne: playerOne, playerTwo: playerTwo, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team)
}
}
return results
} else {
return []
}
}
func importDataFromFFT() async -> String? { func importDataFromFFT() async -> String? {
if let importingDate = SourceFile.mostRecentDateAvailable { if let importingDate = SourceFile.mostRecentDateAvailable {
for source in SourceFile.allCases { for source in SourceFile.allCases {

@ -35,10 +35,11 @@ class NetworkManager {
let task = try await URLSession.shared.download(for: request) let task = try await URLSession.shared.download(for: request)
if let urlResponse = task.1 as? HTTPURLResponse { if let urlResponse = task.1 as? HTTPURLResponse {
print(urlResponse.statusCode)
if urlResponse.statusCode == 200 { if urlResponse.statusCode == 200 {
try FileManager.default.copyItem(at: task.0, to: destinationFileUrl) try FileManager.default.copyItem(at: task.0, to: destinationFileUrl)
print("dl rank data ok", lastDateString, fileName)
} else if urlResponse.statusCode == 404 && fileName == "MESSIEURS" { } else if urlResponse.statusCode == 404 && fileName == "MESSIEURS" {
print("dl rank data failed", lastDateString, fileName)
throw NetworkManagerError.fileNotYetAvailable throw NetworkManagerError.fileNotYetAvailable
} }
} }

@ -7,6 +7,97 @@
import Foundation import Foundation
class SourceFileManager {
static let shared = SourceFileManager()
func fetchData() async {
if let mostRecent = SourceFile.mostRecentDateAvailable, let current = Calendar.current.date(byAdding: .month, value: 1, to: mostRecent), current > mostRecent {
await fetchData(fromDate: current)
} else {
await fetchData(fromDate: Date())
}
}
func _removeAllData(fromDate current: Date) {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
files.forEach { fileName in
NetworkManager.shared.removeRankingData(lastDateString: lastStringDate, fileName: fileName)
}
}
func fetchData(fromDate current: Date) async {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
do {
try await withThrowingTaskGroup(of: Void.self) { group in // Mark 1
for file in files {
group.addTask {
try await NetworkManager.shared.downloadRankingData(lastDateString: lastStringDate, fileName: file)
}
}
try await group.waitForAll()
}
if current < Date() {
if let nextCurrent = Calendar.current.date(byAdding: .month, value: 1, to: current) {
await fetchData(fromDate: nextCurrent)
}
}
} catch {
print("downloadRankingData", error)
if SourceFile.mostRecentDateAvailable == nil {
if let previousDate = Calendar.current.date(byAdding: .month, value: -1, to: current) {
await fetchData(fromDate: previousDate)
}
}
}
}
func getAllFiles() async {
let dates = monthsBetweenDates(startDateString: "08-2022", endDateString: Date().monthYearFormatted)
.compactMap {
URL.importDateFormatter.date(from: $0)
}
.filter { date in
SourceFile.allFiles.contains(where: { $0.dateFromPath == date }) == false
}
await dates.concurrentForEach { date in
await self.fetchData(fromDate: date)
}
}
func monthsBetweenDates(startDateString: String, endDateString: String) -> [String] {
let dateFormatter = URL.importDateFormatter
guard let startDate = dateFormatter.date(from: startDateString),
let endDate = dateFormatter.date(from: endDateString) else {
return []
}
var months: [String] = []
var currentDate = startDate
let calendar = Calendar.current
while currentDate <= endDate {
let monthString = dateFormatter.string(from: currentDate)
months.append(monthString)
guard let nextMonthDate = calendar.date(byAdding: .month, value: 1, to: currentDate) else {
break
}
currentDate = nextMonthDate
}
return months
}
}
enum SourceFile: String, CaseIterable { enum SourceFile: String, CaseIterable {
case dames = "DAMES" case dames = "DAMES"
case messieurs = "MESSIEURS" case messieurs = "MESSIEURS"

@ -16,18 +16,18 @@ struct Line: Identifiable {
let rawValue: String let rawValue: String
let data: [String?] let data: [String?]
func updatePlayer(player: Player) { // func updatePlayer(player: Player) {
player.clubName = data[11] // player.clubName = data[11]
player.ligue = data[9] // player.ligue = data[9]
player.country = data[4] // player.country = data[4]
player.license = data[5] // player.license = data[5]
player.inscriptionRank = rank // player.inscriptionRank = rank
player.currentRank = rank // player.currentRank = rank
player.tournamentCount = Int16(tournamentCount) // player.tournamentCount = Int16(tournamentCount)
player.points = points ?? 0 // player.points = points ?? 0
player.codeClub = data[10] // player.codeClub = data[10]
player.assimilation = assimilation // player.assimilation = assimilation
} // }
var points: Double? { var points: Double? {
if let pointsValue { if let pointsValue {
@ -55,9 +55,9 @@ struct Line: Identifiable {
data[1] ?? nil data[1] ?? nil
} }
var rank: Int64 { var rank: Int {
if let rankValue { if let rankValue {
return Int64(rankValue) ?? 0 return Int(rankValue) ?? 0
} }
return 0 return 0
} }
@ -83,7 +83,7 @@ struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
self.url = url self.url = url
self.seperator = seperator self.seperator = seperator
self.lineIterator = url.lines.makeAsyncIterator() self.lineIterator = url.lines.makeAsyncIterator()
self.maleData = url.path().contains(PDFSource.messieurs.rawValue) self.maleData = url.path().contains(SourceFile.messieurs.rawValue)
} }
mutating func last() async throws -> Line? { mutating func last() async throws -> Line? {

@ -190,6 +190,10 @@ struct InscriptionManagerFileInputTip: Tip {
} }
struct InscriptionManagerWomanRankTip: Tip { struct InscriptionManagerWomanRankTip: Tip {
var image: Image? {
Image(systemName: "figure.dress.line.vertical.figure")
}
var title: Text { var title: Text {
Text("Rang d'une joueuse dans un tournoi messieurs") Text("Rang d'une joueuse dans un tournoi messieurs")
} }

@ -66,7 +66,7 @@ struct StepperView: View {
} }
fileprivate func _plusIsDisabled() -> Bool { fileprivate func _plusIsDisabled() -> Bool {
count >= (maximum ?? Int.max) count >= (maximum ?? 100_000)
} }
fileprivate func _add() { fileprivate func _add() {

@ -60,19 +60,19 @@ struct ContentView: View {
// let club: Club = Club(name: "test\(id)", address: "some address") // let club: Club = Club(name: "test\(id)", address: "some address")
// self.dataStore.clubs.addOrUpdate(instance: club) // self.dataStore.clubs.addOrUpdate(instance: club)
for _ in 0...20 { // for _ in 0...20 {
var clubs: [Club] = [] // var clubs: [Club] = []
for _ in 0...20 { // for _ in 0...20 {
let id = (0...1000000).randomElement()! // let id = (0...1000000).randomElement()!
let club: Club = Club(name: "test\(id)", acronym: "test", address: "some address") // let club: Club = Club(name: "test\(id)", acronym: "test", address: "some address")
clubs.append(club) // clubs.append(club)
} // }
do { // do {
try self.dataStore.clubs.append(contentOfs: clubs) // try self.dataStore.clubs.append(contentOfs: clubs)
} catch { // } catch {
Logger.error(error) // Logger.error(error)
} // }
} // }
} }
} }

@ -80,7 +80,7 @@ struct EventCreationView: View {
} }
try? dataStore.events.addOrUpdate(instance: event) try? dataStore.events.addOrUpdate(instance: event)
} }
try? dataStore.tournaments.append(contentOfs: tournaments) try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
dismiss() dismiss()
} }
.clipShape(Capsule()) .clipShape(Capsule())
@ -115,12 +115,6 @@ struct EventCreationView: View {
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") { RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance() let tournament = Tournament.newEmptyInstance()
// let tournament = Tournament(context: viewContext)
// tournament.tournamentLevel = TournamentLevel.mostUsed(tournaments: tournaments)
// tournament.tournamentCategory = TournamentCategory.mostUsed(tournaments: tournaments).next
// tournament.federalTournamentAge = FederalTournamentAge.mostUsed(tournaments: tournaments)
//
self.tournaments.append(tournament) self.tournaments.append(tournament)
} }
} }

@ -23,9 +23,10 @@ struct GroupStageView: View {
var groupStageView: some View { var groupStageView: some View {
ForEach(0..<(groupStage.size), id: \.self) { index in ForEach(0..<(groupStage.size), id: \.self) { index in
// let entrant : Entrant? = runningGroupStageOrderedByScore ? groupStage.orderedByScore[Int(index)] : groupStage.entrantAtIndex(Int(index)) // let entrant : Entrant? = runningGroupStageOrderedByScore ? groupStage.orderedByScore[Int(index)] : groupStage.entrantAtIndex(Int(index))
// if let entrant { if let team = groupStage.teamsAt(index) {
Text(team.teamLabel())
// GroupStageEntrantMenuView(entrant: entrant, groupStage: groupStage, index: index.intValue) // GroupStageEntrantMenuView(entrant: entrant, groupStage: groupStage, index: index.intValue)
// } else { } else {
Menu { Menu {
// Section { // Section {
// EntrantPickerView(groupStage: groupStage, index: Int(index)) // EntrantPickerView(groupStage: groupStage, index: Int(index))
@ -48,7 +49,7 @@ struct GroupStageView: View {
Text("Aucune équipe") Text("Aucune équipe")
} }
} }
// } }
} }
} }

@ -56,6 +56,7 @@ struct MainView: View {
.environmentObject(dataStore) .environmentObject(dataStore)
.task { .task {
await self._checkSourceFileAvailability() await self._checkSourceFileAvailability()
await self._downloadPreviousDate()
} }
.refreshable { .refreshable {
Task { Task {
@ -113,7 +114,7 @@ struct MainView: View {
print("check files on internet") print("check files on internet")
print("check if any files on internet are more recent than here") print("check if any files on internet are more recent than here")
checkingFiles = true checkingFiles = true
await fetchData() await SourceFileManager.shared.fetchData()
checkingFilesAttempt += 1 checkingFilesAttempt += 1
checkingFiles = false checkingFiles = false
@ -130,6 +131,8 @@ struct MainView: View {
await _calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) await _calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate)
} }
importingFiles = false importingFiles = false
_downloadPreviousDate()
} }
} }
@ -138,55 +141,11 @@ struct MainView: View {
lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false) lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false)
} }
private func fetchData() async { private func _downloadPreviousDate() {
if let mostRecent = SourceFile.mostRecentDateAvailable, let current = Calendar.current.date(byAdding: .month, value: 1, to: mostRecent), current > mostRecent { Task {
await fetchData(fromDate: current) await SourceFileManager.shared.getAllFiles()
} else {
await fetchData(fromDate: Date())
}
}
private func _removeAllData(fromDate current: Date) {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
files.forEach { fileName in
NetworkManager.shared.removeRankingData(lastDateString: lastStringDate, fileName: fileName)
}
}
private func fetchData(fromDate current: Date) async {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
do {
try await withThrowingTaskGroup(of: Void.self) { group in // Mark 1
for file in files {
group.addTask {
try await NetworkManager.shared.downloadRankingData(lastDateString: lastStringDate, fileName: file)
}
}
try await group.waitForAll()
}
if current < Date() {
if let nextCurrent = Calendar.current.date(byAdding: .month, value: 1, to: current) {
await fetchData(fromDate: nextCurrent)
}
}
} catch {
print("downloadRankingData", error)
if _mostRecentDateAvailable == nil {
if let previousDate = Calendar.current.date(byAdding: .month, value: -1, to: current) {
await fetchData(fromDate: previousDate)
}
}
} }
} }
} }
fileprivate extension View { fileprivate extension View {

@ -12,7 +12,7 @@ struct TeamDetailView: View {
var team: TeamRegistration var team: TeamRegistration
var body: some View { var body: some View {
if team.players().isEmpty { if team.unsortedPlayers().isEmpty {
Text("Aucun joueur, espace réservé") Text("Aucun joueur, espace réservé")
} else { } else {
ForEach(team.players()) { player in ForEach(team.players()) { player in

@ -8,11 +8,292 @@
import SwiftUI import SwiftUI
struct FileImportView: View { struct FileImportView: View {
@Environment(Tournament.self) var tournament: Tournament
@Environment(\.dismiss) private var dismiss
let fileContent: String?
@State private var teams: [FileImportManager.TeamHolder] = []
@State private var isShowing = false
@State private var didImport = false
@State private var convertingFile = false
@State private var errorMessage: String? = nil
@State private var forceRankUpdate: Bool = false
@State private var selectedOptions: Set<TeamImportStrategy> = Set()
@State private var fileProvider: FileImportManager.FileProvider = .frenchFederation
let federalLink = URL(string: "https://beach-padel.app.fft.fr/beachja/index/")!
private var filteredTeams: [FileImportManager.TeamHolder] {
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight)
}
var body: some View { var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) List {
if teams.isEmpty {
Section {
Link(destination: federalLink) {
Label("Ouvrir beach-padel.app.fft.fr", systemImage: "tennisball")
}
Button {
convertingFile = false
isShowing.toggle()
} label: {
Label("Choisir le fichier", systemImage: "square.and.arrow.down")
}
} header: {
} footer: {
VStack(alignment: .leading) {
Text("Fichier provenant de beach-padel.app.fft.fr")
Text("Format XLS ou CSV, onglet inscriptions ou joueurs")
}
}
}
if filteredTeams.isEmpty == false {
Section {
ForEach(TeamImportStrategy.allCases, id: \.self) { strategy in
LabeledContent {
Toggle(isOn: .init(get: {
selectedOptions.contains(strategy)
}, set: { selected in
if selected {
selectedOptions.insert(strategy)
} else {
selectedOptions.remove(strategy)
}
})) {}
} label: {
Text(strategy.titleLabel())
Text(strategy.descriptionLabel())
}
}
} header: {
Text("Stratégie d'importation")
}
}
if convertingFile {
Section {
LabeledContent {
ProgressView()
} label: {
Text("Importation en cours")
}
}
}
if let errorMessage {
Section {
Text(errorMessage)
} header: {
Text("Erreur")
}
}
// if tournament.entriesCount > 0 {
// Section {
// if tournament.inscriptionClosed {
// Label("Les inscriptions clôturées", systemImage: "lock")
// Text("Si le poids des équipes a changé, aucun déplacement entre les poules et le tableau n'est possible. Par contre, le classement sera mis à jour au sein des poules et du tableau, respectivement, en fonction de leur nouveau poids.")
// Toggle(isOn: $forceRankUpdate) {
// Text("Ne pas en tenir compte")
// }
// } else {
// Label("Les inscriptions sont ouvertes", systemImage: "lock.open")
// Text("Si le poids des équipes a changé, le classement de toutes les équipes sera mis à jour en fonction de leur nouveau poids.")
// }
// }
// }
if filteredTeams.isEmpty && teams.isEmpty == false {
@Bindable var tournament = tournament
Section {
Text("Aucune équipe \(tournament.tournamentCategory.importingRawValue) détectée mais \(teams.count) équipes sont dans le fichier")
Picker(selection: $tournament.tournamentCategory) {
ForEach(TournamentCategory.allCases) { category in
Text(category.importingRawValue).tag(category)
}
} label: {
Text("Modifier la catégorie du tournoi ?")
}
.onChange(of: tournament.tournamentCategory) {
save()
}
}
} else if teams.isEmpty && didImport == true {
Section {
ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash")
}
} else if didImport {
Section {
let previousTeams = tournament.teams()
ForEach(filteredTeams) { team in
LabeledContent {
HStack {
if let previousTeam = team.previousTeam {
Text(previousTeam.formattedSeed(in: previousTeams))
}
Text("->")
Text(team.formattedSeed(in: filteredTeams))
}
} label: {
VStack(alignment: .leading) {
Text(team.playerOne.playerLabel())
Text(team.playerTwo.playerLabel())
}
}
}
} header: {
HStack {
Text("Équipes \(tournament.tournamentCategory.importingRawValue) détectées dans ce fichier")
Spacer()
Text(filteredTeams.count.formatted())
}
}
}
}
.onAppear {
if let fileContent {
Task {
await _startImport(fileContent: fileContent)
}
}
}
.fileImporter(isPresented: $isShowing, allowedContentTypes: [.spreadsheet, .commaSeparatedText], allowsMultipleSelection: false, onCompletion: { results in
switch results {
case .success(let fileurls):
if let selectedFile = fileurls.first {
if selectedFile.startAccessingSecurityScopedResource() {
convertingFile = true
teams.removeAll()
Task {
do {
var fileContent: String?
if selectedFile.lastPathComponent.hasSuffix("xls") {
fileContent = try await CloudConvert.manager.uploadFile(selectedFile)
} else {
fileContent = try String(contentsOf: selectedFile)
}
if let fileContent {
await _startImport(fileContent: fileContent)
}
selectedFile.stopAccessingSecurityScopedResource()
} catch {
errorMessage = error.localizedDescription
}
}
} else {
// Handle denied access
}
}
case .failure(let error):
errorMessage = error.localizedDescription
}
})
.onOpenURL { url in
do {
let fileContent = try String(contentsOf: url)
Task {
await _startImport(fileContent: fileContent)
}
} catch {
errorMessage = error.localizedDescription
}
}
.navigationTitle("Import")
.navigationBarTitleDisplayMode(.large)
.onDisappear {
//viewContext.rollback()
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) {
dismiss()
}
}
ToolbarItem(placement: .bottomBar) {
Button {
// tournament.updateTournamentEntriesWith(teams: filteredTeams, viewContext: viewContext)
// save()
tournament.importTeams(filteredTeams)
dismiss()
} label: {
Text("Valider")
}
.buttonStyle(.borderedProminent)
.disabled(teams.isEmpty)
}
}
}
func _startImport(fileContent: String) async {
await MainActor.run {
convertingFile = true
teams.removeAll()
}
self.teams = await FileImportManager.shared.createTeams(from: fileContent, tournament: tournament, fileProvider: fileProvider)
await MainActor.run {
convertingFile = false
didImport = true
}
}
func save() {
} }
} }
#Preview { #Preview {
FileImportView() FileImportView(fileContent: nil)
.environment(Tournament.mock())
}
enum TeamImportStrategy: CaseIterable {
case keepPreviousData
case notFoundAreWalkOut
case deleteBeforeImport
case updatePosition
case updatePositionWithinBlock
func titleLabel() -> String {
switch self {
case .keepPreviousData:
"Gardez les données existantes"
case .notFoundAreWalkOut:
"Mettre les équipes manquantes WO"
case .deleteBeforeImport:
"Effacer avant d'importer"
case .updatePosition:
"Modifier les positions"
case .updatePositionWithinBlock:
"Modidier les positions par bloc"
}
}
func descriptionLabel() -> String {
switch self {
case .keepPreviousData:
"Si l'équipe déjà présente, garde la date d'inscription"
case .notFoundAreWalkOut:
"Si une équipe déjà présente n'est pas dans la nouvelle liste, elle sera mise à WO"
case .deleteBeforeImport:
"Écrase les données précédentes avant d'importer"
case .updatePosition:
"Mets à jour les positions si changement de poids d'équipe"
case .updatePositionWithinBlock:
"Mets à jour les positions seulement au sein du tableau et des poules séparement"
}
}
} }

@ -55,9 +55,7 @@ struct InscriptionTipsView: View {
} }
Section { Section {
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez encore aucune équipe dans votre liste d'attente."))
Text("Vous n'avez encore aucune équipe dans votre liste d'attente.")
)
} }
// if let mostRecentDate, let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.isOver == false { // if let mostRecentDate, let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.isOver == false {

@ -13,7 +13,7 @@ struct TournamentFieldsManagerView: View {
var body: some View { var body: some View {
@Bindable var tournament = tournament @Bindable var tournament = tournament
Stepper(value: $tournament.courtCount, in: 1...Int.max) { Stepper(value: $tournament.courtCount, in: 1...100_000) {
LabeledContent { LabeledContent {
Text(tournament.courtCount.formatted()) Text(tournament.courtCount.formatted())
} label: { } label: {

@ -8,11 +8,87 @@
import SwiftUI import SwiftUI
struct UpdateSourceRankDateView: View { struct UpdateSourceRankDateView: View {
@EnvironmentObject var dataStore: DataStore
@Binding var currentRankSourceDate: Date?
@Binding var confirmUpdateRank: Bool
@State private var updatingRank = false
var tournament: Tournament
var body: some View { var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) NavigationStack {
List {
let suffix = (false ? "Les incriptions sont closes. Les équipes ne pourront pas être déplacé entre les poules et le tableau. Seule leur position au sein des poules et du tableau, respectivement, seront modifiée." : "Les inscriptions sont toujours ouvertes. Les équipes pourront être déplacé entre les poules et le tableau.")
Section {
Text("Vous êtes sur le point de mettre à jour les rangs des équipes, cela affectera leur position." + "\n" + suffix)
}
RowButtonView(title: "Valider") {
updatingRank = true
//buildMoveArray()
Task {
do {
try await tournament.updateRank(to: currentRankSourceDate)
await MainActor.run {
if tournament.state() == .build {
//manageEntriesMovement()
} else {
//save()
tournament.unsortedPlayers().forEach { player in
player.setWeight(in: tournament)
}
try? dataStore.playerRegistrations.addOrUpdate(contentOfs: tournament.unsortedPlayers())
tournament.unsortedTeams().forEach { team in
team.setWeight(from: team.players())
}
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
try? dataStore.tournaments.addOrUpdate(instance: tournament)
}
updatingRank = false
confirmUpdateRank = false
}
} catch {
}
}
}.disabled(updatingRank)
if updatingRank {
Section {
let message = currentRankSourceDate == nil ? "Mise à jour des classements" : "Mise à jour des classements provenant de \(currentRankSourceDate!.monthYearFormatted) pour tous les joueurs."
ContentUnavailableView("Mise à jour", systemImage: "exclamationmark.triangle", description: Text(message))
}
Section {
HStack {
Spacer()
ProgressView()
Spacer()
}
}
}
}
.navigationTitle("Attention")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button(role: .cancel) {
confirmUpdateRank = false
} label: {
Text("Annuler")
}
}
}
}
.interactiveDismissDisabled(updatingRank)
} }
} }
#Preview { #Preview {
UpdateSourceRankDateView() UpdateSourceRankDateView(currentRankSourceDate: .constant(Date()), confirmUpdateRank: .constant(true), tournament: Tournament.mock())
} }

@ -18,15 +18,35 @@ struct InscriptionManagerView: View {
var tournament: Tournament var tournament: Tournament
@State private var searchField: String = "" @State private var searchField: String = ""
@State private var presentSearch: Bool = false
@State private var presentPlayerSearch: Bool = false @State private var presentPlayerSearch: Bool = false
@State private var presentPlayerCreation: Bool = false @State private var presentPlayerCreation: Bool = false
@State private var presentImportView: Bool = false
@State private var createdPlayers: Set<PlayerRegistration> = Set() @State private var createdPlayers: Set<PlayerRegistration> = Set()
@State private var testCreatedPlayers: Set<String> = Set() @State private var testCreatedPlayers: Set<String> = Set()
@State private var editedTeam: TeamRegistration? @State private var editedTeam: TeamRegistration?
@State private var pasteString: String? @State private var pasteString: String?
@State private var currentRankSourceDate: Date?
@State private var confirmUpdateRank = false
@State private var updatingRank = false
let slideToDeleteTip = SlideToDeleteTip() let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
let categoryOption: PlayerFilterOption
let filterable: Bool
init(tournament: Tournament) {
self.tournament = tournament
_currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate)
switch tournament.tournamentCategory {
case .women:
categoryOption = .female
filterable = false
default:
categoryOption = .all
filterable = true
}
}
private func _pastePredicate(pasteField: String, mostRecentDate: Date?) -> NSPredicate? { private func _pastePredicate(pasteField: String, mostRecentDate: Date?) -> NSPredicate? {
let text = pasteField.canonicalVersion let text = pasteField.canonicalVersion
@ -73,6 +93,7 @@ struct InscriptionManagerView: View {
fetchPlayers.first(where: { id == $0.license }) fetchPlayers.first(where: { id == $0.license })
}.forEach { player in }.forEach { player in
let player = PlayerRegistration(importedPlayer: player) let player = PlayerRegistration(importedPlayer: player)
player.setWeight(in: tournament)
currentSelection.insert(player) currentSelection.insert(player)
} }
@ -116,8 +137,14 @@ struct InscriptionManagerView: View {
} }
if editedTeam == nil { if editedTeam == nil {
RowButtonView(title: "Ajouter l'équipe") { if testCreatedPlayers.isEmpty {
_createTeam() RowButtonView(title: "Bloquer une place") {
_createTeam()
}
} else {
RowButtonView(title: "Ajouter l'équipe") {
_createTeam()
}
} }
} else { } else {
RowButtonView(title: "Modifier l'équipe") { RowButtonView(title: "Modifier l'équipe") {
@ -127,16 +154,6 @@ struct InscriptionManagerView: View {
if let pasteString { if let pasteString {
// if pasteString.licencesFound().count == 2 {
// let hits = fetchPlayers.filter { $0.hitForSearch(pasteString) == 100 }
// if hits.count == 2 {
// ForEach(hits) { hit in
//
// createdPlayers
// }
// }
// }
//
Section { Section {
Text(pasteString) Text(pasteString)
} footer: { } footer: {
@ -152,12 +169,29 @@ struct InscriptionManagerView: View {
} }
} }
Section { if fetchPlayers.isEmpty {
ForEach(fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) })) { player in ContentUnavailableView {
ImportedPlayerView(player: player).tag(player.license!) Label("Aucun résultat", systemImage: "person.2.slash")
} description: {
Text("Aucun joueur classé n'a été trouvé dans ce message.")
} actions: {
RowButtonView(title: "Créer un joueur non classé") {
presentPlayerCreation = true
}
RowButtonView(title: "Effacer cette recherche") {
self.pasteString = nil
}
}
} else {
Section {
ForEach(fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) })) { player in
ImportedPlayerView(player: player).tag(player.license!)
}
} header: {
Text(fetchPlayers.count.formatted() + " résultat" + fetchPlayers.count.pluralSuffix)
} }
} header: {
Text(fetchPlayers.count.formatted() + " résultat" + fetchPlayers.count.pluralSuffix)
} }
} }
} }
@ -188,28 +222,76 @@ struct InscriptionManagerView: View {
private func _teamRegisteredView() -> some View { private func _teamRegisteredView() -> some View {
List { List {
Section { Section {
if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false { rankingDateSourcePickerView(showDateInLabel: true)
TipView(inscriptionManagerWomanRankTip) let duplicates = tournament.duplicates()
.tipStyle(tint: nil) DisclosureGroup {
if duplicates.isEmpty == false {
ForEach(duplicates) { player in
PlayerView(player: player)
}
}
} label: {
LabeledContent {
Text(duplicates.count.formatted())
} label: {
Text("Doublons")
}
} }
} header: { } header: {
Text("Informations") Text("Informations")
} }
if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false {
Section {
TipView(inscriptionManagerWomanRankTip)
.tipStyle(tint: nil)
}
}
Section { Section {
TipView(slideToDeleteTip) TipView(slideToDeleteTip)
.tipStyle(tint: nil) .tipStyle(tint: nil)
} }
ForEach(tournament.teams()) { team in let unfilteredTeams = tournament.teams()
let teams = searchField.isEmpty ? unfilteredTeams : unfilteredTeams.filter({ $0.contains(searchField.canonicalVersion) })
if teams.isEmpty && searchField.isEmpty == false {
ContentUnavailableView {
Label("Aucun résultat", systemImage: "person.2.slash")
} description: {
Text("\(searchField) est introuvable dans les équipes inscrites.")
} actions: {
RowButtonView(title: "Modifier la recherche") {
searchField = ""
presentSearch = true
}
RowButtonView(title: "Créer une équipe") {
Task {
await MainActor.run() {
// fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: tournament.rankSourceDate)
pasteString = searchField
}
}
}
RowButtonView(title: "D'accord") {
searchField = ""
presentSearch = false
}
}
}
ForEach(teams) { team in
Section { Section {
TeamRowView(team: team) TeamRowView(team: team)
} header: { } header: {
HStack { HStack {
Text("Équipe") Text("Équipe " + team.formattedSeed(in: unfilteredTeams))
Spacer() Spacer()
Text(team.computedRank().formatted()) Text(team.weight.formatted())
} }
} footer: { } footer: {
HStack { HStack {
@ -217,7 +299,7 @@ struct InscriptionManagerView: View {
Menu { Menu {
Button("Éditer") { Button("Éditer") {
editedTeam = team editedTeam = team
team.players().forEach { player in team.unsortedPlayers().forEach { player in
createdPlayers.insert(player) createdPlayers.insert(player)
testCreatedPlayers.insert(player.id) testCreatedPlayers.insert(player.id)
} }
@ -229,14 +311,14 @@ struct InscriptionManagerView: View {
LabelDelete() LabelDelete()
} }
} label: { } label: {
LabelOptions() LabelOptions().labelStyle(.titleOnly)
} }
} }
} }
.headerProminence(.increased) .headerProminence(.increased)
} }
} }
.searchable(text: $searchField) .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites"))
} }
var body: some View { var body: some View {
@ -244,7 +326,7 @@ struct InscriptionManagerView: View {
_managementView() _managementView()
if testCreatedPlayers.isEmpty == false || pasteString != nil || editedTeam != nil { if testCreatedPlayers.isEmpty == false || pasteString != nil || editedTeam != nil {
_buildingTeamView() _buildingTeamView()
} else if tournament.teams().isEmpty { } else if tournament.unsortedTeams().isEmpty {
InscriptionTipsView() InscriptionTipsView()
} else { } else {
_teamRegisteredView() _teamRegisteredView()
@ -255,6 +337,7 @@ struct InscriptionManagerView: View {
SelectablePlayerListView(allowSelection: -1, filterOption: _filterOption()) { players in SelectablePlayerListView(allowSelection: -1, filterOption: _filterOption()) { players in
players.forEach { player in players.forEach { player in
let newPlayer = PlayerRegistration(importedPlayer: player) let newPlayer = PlayerRegistration(importedPlayer: player)
newPlayer.setWeight(in: tournament)
createdPlayers.insert(newPlayer) createdPlayers.insert(newPlayer)
testCreatedPlayers.insert(newPlayer.id) testCreatedPlayers.insert(newPlayer.id)
} }
@ -267,6 +350,23 @@ struct InscriptionManagerView: View {
testCreatedPlayers.insert(p.id) testCreatedPlayers.insert(p.id)
} }
} }
.sheet(isPresented: $presentImportView) {
NavigationStack {
FileImportView(fileContent: nil)
}
}
.onChange(of: currentRankSourceDate) {
// if let currentRankSourceDate, tournament.currentRankSourceDate != currentRankSourceDate {
// confirmUpdateRank = true
// }
confirmUpdateRank = true
}
.sheet(isPresented: $confirmUpdateRank, onDismiss: {
currentRankSourceDate = tournament.rankSourceDate
}) {
UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament)
}
.toolbar { .toolbar {
if testCreatedPlayers.isEmpty == false { if testCreatedPlayers.isEmpty == false {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
@ -276,10 +376,22 @@ struct InscriptionManagerView: View {
} }
} }
} }
ToolbarItem(placement: .topBarTrailing) {
Menu {
Button {
presentImportView = true
} label: {
Label("Importer", systemImage: "square.and.arrow.down")
}
} label: {
LabelOptions()
}
}
} }
.navigationBarBackButtonHidden(testCreatedPlayers.isEmpty == false) .navigationBarBackButtonHidden(testCreatedPlayers.isEmpty == false)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Inscription") .navigationTitle("Inscriptions")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
@ -303,7 +415,7 @@ struct InscriptionManagerView: View {
guard let first = strings.first else { return } guard let first = strings.first else { return }
Task { Task {
await MainActor.run() { await MainActor.run() {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: nil) // fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: tournament.rankSourceDate)
pasteString = first pasteString = first
} }
} }
@ -329,6 +441,35 @@ struct InscriptionManagerView: View {
.padding(16) .padding(16)
} }
@ViewBuilder
func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View {
Section {
Picker(selection: $currentRankSourceDate) {
if currentRankSourceDate == nil {
Text("inconnu").tag(nil as Date?)
}
let dates = Array(Set(SourceFile.allFilesSortedByDate(tournament.tournamentCategory.rankingDataSourceMale).map({ $0.dateFromPath }))).sorted().reversed()
ForEach(dates, id: \.self) { date in
Text(date.monthYearFormatted).tag(date as Date?)
}
} label: {
Text("Classement mensuel utilisé")
if showDateInLabel {
if let currentRankSourceDate {
Text(currentRankSourceDate.monthYearFormatted)
} else {
Text("Choisir le mois")
}
}
}
.pickerStyle(.menu)
}
}
private func _addPlayerSex() -> Int { private func _addPlayerSex() -> Int {
switch tournament.tournamentCategory { switch tournament.tournamentCategory {
case .men: case .men:

@ -35,7 +35,7 @@ struct TournamentView: View {
NavigationLink(value: Screen.inscription) { NavigationLink(value: Screen.inscription) {
LabeledContent { LabeledContent {
Text(tournament.teams().count.formatted()) Text(tournament.unsortedTeams().count.formatted())
} label: { } label: {
Text("Inscriptions") Text("Inscriptions")
} }

Loading…
Cancel
Save