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. 81
      PadelClub/Data/TeamRegistration.swift
  7. 173
      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. 207
      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 */; };
FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.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 */; };
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.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 */; };
FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.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 */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.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 */; };
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.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 */; };
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 */; };
@ -201,6 +247,51 @@
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>"; };
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>"; };
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>"; };
@ -266,6 +357,9 @@
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>"; };
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>"; };
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>"; };
@ -277,7 +371,6 @@
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>"; };
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>"; };
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>"; };
@ -334,6 +427,7 @@
C425D3FF2B6D249D002A7B48 /* PadelClub */ = {
isa = PBXGroup;
children = (
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */,
C425D44E2B6D24E1002A7B48 /* LeStorage.xcodeproj */,
C425D4002B6D249D002A7B48 /* PadelClubApp.swift */,
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */,
@ -343,6 +437,7 @@
FFF8ACD02B9238A2008466FA /* Manager */,
FFF8ACD72B923F26008466FA /* Extensions */,
C425D4042B6D249E002A7B48 /* Assets.xcassets */,
FF0EC54D2BB195CA0056B6D1 /* CSV */,
C425D4062B6D249E002A7B48 /* Preview Content */,
);
path = PadelClub;
@ -494,6 +589,55 @@
path = Player;
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 */ = {
isa = PBXGroup;
children = (
@ -525,6 +669,7 @@
FF70916B2B91005400AB08DA /* TournamentView.swift */,
FF8F26402BADFC8700650388 /* TournamentInitView.swift */,
FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */,
FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */,
FF3F74F92B91A018004CFE0E /* Screen */,
FF3F74F82B919FB2004CFE0E /* Shared */,
);
@ -645,6 +790,7 @@
FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */,
FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */,
FF089EB72BB00ABF00F0AEC7 /* InscriptionTipsView.swift */,
FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -705,11 +851,13 @@
isa = PBXGroup;
children = (
FF1DC5582BAB767000FD8220 /* Tips.swift */,
FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */,
FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */,
FFF8ACD12B9238C3008466FA /* FileImportManager.swift */,
FFF8ACD32B92392C008466FA /* SourceFileManager.swift */,
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */,
FF8F26352BAD523300650388 /* PadelRule.swift */,
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */,
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */,
FF6EC9072B947A1E00EA7F5A /* Network */,
);
path = Manager;
@ -857,6 +1005,48 @@
buildActionMask = 2147483647;
files = (
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -885,6 +1075,7 @@
C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */,
FF7091662B90F0B000AB08DA /* TabDestination.swift in Sources */,
FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */,
FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */,
C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */,
FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */,
C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */,
@ -899,6 +1090,7 @@
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */,
FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */,
FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */,
C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */,
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */,
FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */,
@ -939,6 +1131,7 @@
FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */,
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */,
FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */,
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */,
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */,
FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */,
FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */,
@ -948,6 +1141,7 @@
FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */,
FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
@ -960,7 +1154,6 @@
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */,
C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */,
FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */,
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */,
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF089EB82BB00ABF00F0AEC7 /* InscriptionTipsView.swift in Sources */,
@ -986,6 +1179,7 @@
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */,
FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */,
FF0EC5222BB173E70056B6D1 /* UpdateSourceRankDateView.swift in Sources */,
C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1153,6 +1347,7 @@
DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_PREVIEWS = 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_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -1183,6 +1378,7 @@
DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_PREVIEWS = 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_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;

@ -43,15 +43,15 @@ class DataStore: ObservableObject {
// store.addMigration(Migration<TournamentV1, TournamentV2>(version: 2))
// store.addMigration(Migration<TournamentV2, Tournament>(version: 3))
self.clubs = store.registerCollection(synchronized: false)
self.tournaments = store.registerCollection(synchronized: false)
self.events = store.registerCollection(synchronized: false)
self.groupStages = store.registerCollection(synchronized: false)
self.teamScores = store.registerCollection(synchronized: false)
self.teamRegistrations = store.registerCollection(synchronized: false)
self.playerRegistrations = store.registerCollection(synchronized: false)
self.rounds = store.registerCollection(synchronized: false)
self.matches = store.registerCollection(synchronized: false)
self.clubs = store.registerCollection(synchronized: false, indexed: true)
self.tournaments = store.registerCollection(synchronized: false, indexed: true)
self.events = store.registerCollection(synchronized: false, indexed: true)
self.groupStages = store.registerCollection(synchronized: false, indexed: true)
self.teamScores = store.registerCollection(synchronized: false, indexed: true)
self.teamRegistrations = store.registerCollection(synchronized: false, indexed: true)
self.playerRegistrations = store.registerCollection(synchronized: false, indexed: true)
self.rounds = store.registerCollection(synchronized: false, indexed: true)
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.CollectionDidChange, object: nil)

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

@ -23,7 +23,20 @@ extension 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,11 +28,12 @@ class PlayerRegistration: ModelObject, Storable {
var ligueName: String?
var assimilation: String?
// var phoneNumber: String?
// var email: String?
// var birthDate: Date?
// var club: String?
var phoneNumber: String?
var email: String?
var birthdate: 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) {
self.teamRegistration = teamRegistration
self.firstName = firstName
@ -58,6 +59,39 @@ class PlayerRegistration: ModelObject, Storable {
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? {
guard let tournament = team()?.tournament else { return nil }
return Store.main.findById(tournament)
@ -87,22 +121,78 @@ class PlayerRegistration: ModelObject, Storable {
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 {
return rank.ordinalFormatted()
return rank.formatted()
} else {
return "non classé" + (isMalePlayer() ? "" : "e")
}
}
func getRank() -> Int {
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 history(from sources: [CSVParser]) async throws -> Line? {
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 }
var computedRank: Int {
rank ?? tournament()?.unrankValue(for: isMalePlayer()) ?? Int.max
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 rank(for tournamentCategory: TournamentCategory, manMax: Int, womanMax: Int) -> Int {
switch tournamentCategory {
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:
return isMalePlayer() ? computedRank : computedRank + PlayerRegistration.addon(for: computedRank, manMax: manMax, womanMax: womanMax)
case .women, .mix:
return computedRank
weight = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
default:
weight = currentRank
}
}
@ -125,6 +215,11 @@ class PlayerRegistration: ModelObject, Storable {
case _clubName = "clubName"
case _ligueName = "ligueName"
case _assimilation = "assimilation"
case _birthdate = "birthdate"
case _phoneNumber = "phoneNumber"
case _email = "email"
case _weight = "weight"
}
enum PaymentType: Int, CaseIterable, Identifiable {

@ -24,8 +24,14 @@ class TeamRegistration: ModelObject, Storable {
var sourceValue: String?
var logo: 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.groupStage = groupStage
self.registrationDate = registrationDate
@ -37,6 +43,44 @@ class TeamRegistration: ModelObject, Storable {
self.sourceValue = sourceValue
self.logo = logo
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 {
@ -44,12 +88,14 @@ class TeamRegistration: ModelObject, Storable {
}
func updatePlayers(_ players: Set<PlayerRegistration>) {
self.players().forEach { player in
self.unsortedPlayers().forEach { player in
if players.contains(player) == false {
try? DataStore.shared.playerRegistrations.delete(instance: player)
}
}
setWeight(from: Array(players))
players.forEach { player in
player.teamRegistration = id
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
let predicates: [AreInIncreasingOrder] = [
{ $0.sex < $1.sex },
{ $0.computedRank < $1.computedRank },
{ $0.rank ?? 0 < $1.rank ?? 0 },
{ $0.lastName < $1.lastName},
{ $0.firstName < $1.firstName }
]
@ -85,8 +131,13 @@ class TeamRegistration: ModelObject, Storable {
}
}
func computedRank() -> Int {
(players().prefix(significantPlayerCount()).map { $0.computedRank } + missing().map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount()).reduce(0,+)
func unsortedPlayers() -> [PlayerRegistration] {
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 {
@ -94,7 +145,6 @@ class TeamRegistration: ModelObject, Storable {
}
func mandatoryPlayerType() -> [Int] {
guard let tournamentCategory = tournamentObject()?.tournamentCategory else { return [] }
switch tournamentCategory {
case .mix:
return [0, 1]
@ -105,8 +155,10 @@ class TeamRegistration: ModelObject, Storable {
}
}
func missing() -> [Int] {
let s = players().map { $0.sex }
func missingPlayerType() -> [Int] {
let players = unsortedPlayers()
if players.count < 2 { return [] }
let s = players.map { $0.sex }
var missing = mandatoryPlayerType()
s.forEach { i in
if let index = missing.firstIndex(of: i) {
@ -117,7 +169,7 @@ class TeamRegistration: ModelObject, Storable {
}
func unrankValue(for malePlayer: Bool) -> Int {
tournamentObject()?.unrankValue(for: malePlayer) ?? Int.max
tournamentObject()?.unrankValue(for: malePlayer) ?? 100_000
}
func tournamentObject() -> Tournament? {
@ -125,7 +177,7 @@ class TeamRegistration: ModelObject, Storable {
}
override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.players())
try Store.main.deleteDependencies(items: self.unsortedPlayers())
}
enum CodingKeys: String, CodingKey {
@ -141,5 +193,10 @@ class TeamRegistration: ModelObject, Storable {
case _sourceValue = "sourceValue"
case _logo = "logo"
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)
}
func unsortedTeams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }
}
typealias TeamRegistrationCompare = (TeamRegistration, TeamRegistration) -> Bool
func teams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }.sorted {
if $0.computedRank() == $1.computedRank() {
return $0.registrationDate ?? .distantPast < $1.registrationDate ?? .distantPast
} else {
return $0.computedRank() < $1.computedRank()
Store.main.filter { $0.tournament == self.id }
.sorted { (lhs, rhs) in
let predicates: [TeamRegistrationCompare] = [
{ $0.weight < $1.weight },
{ $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] {
teams().flatMap { $0.players() }
unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight)
}
func femalePlayers() -> [PlayerRegistration] {
players().filter({ $0.isMalePlayer() == false })
unsortedPlayers().filter({ $0.isMalePlayer() == false })
}
func unrankValue(for malePlayer: Bool) -> Int? {
@ -135,11 +167,61 @@ class Tournament : ModelObject, Storable {
func significantPlayerCount() -> Int {
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 {
maleUnrankedValue == nil || femaleUnrankedValue == nil
}
func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? {
unsortedTeams().first(where: { $0.includes(players) })
}
func title(_ displayStyle: DisplayStyle = .wide) -> String {
[tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ")
}
@ -163,7 +245,7 @@ class Tournament : ModelObject, Storable {
func qualifiedTeams() -> [TeamRegistration] {
teams().filter({ $0.qualified() })
unsortedTeams().filter({ $0.qualified() })
}
func moreQualifiedToDraw() -> Int {
@ -173,7 +255,7 @@ class Tournament : ModelObject, Storable {
func missingQualifiedFromGroupStages() -> [TeamRegistration] {
if groupStageAdditionalQualified > 0 {
return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in
groupStage.teams[qualifiedPerGroupStage]
groupStage.teams()[qualifiedPerGroupStage]
}
.filter({ $0.qualified() == false })
} else {
@ -228,10 +310,10 @@ class Tournament : ModelObject, Storable {
_groupStages.append(groupStage)
}
try? DataStore.shared.groupStages.append(contentOfs: _groupStages)
try? DataStore.shared.groupStages.addOrUpdate(contentOfs: _groupStages)
groupStages().forEach { $0.buildMatches() }
refreshBrackets()
refreshGroupStages()
}
func resetStructure() {
@ -242,13 +324,12 @@ class Tournament : ModelObject, Storable {
}
func refreshBrackets() {
// completeEntries.forEach { entrant in
// if entrant.bracketPosition > 0 {
// entrant.resetBracketPosition()
// }
// }
//
func refreshGroupStages() {
unsortedTeams().forEach { team in
team.groupStage = nil
team.groupStagePosition = nil
}
if groupStageCount > 0 {
switch groupStageOrderingMode {
case .random:
@ -262,28 +343,31 @@ class Tournament : ModelObject, Storable {
}
func setBrackets(randomize: Bool) {
let numberOfBracketsAsInt = groupStages().count
let groupStages = groupStages()
let numberOfBracketsAsInt = groupStages.count
// let teamsPerBracket = Int(teamsPerBracket)
if groupStageCount != numberOfBracketsAsInt {
buildGroupStages()
return
}
let max = groupStages().map { $0.size }.reduce(0,+)
// var chunks = orderedEntries.filter { $0.wcFinalTable == false }.suffix(Int(max)).chunked(into: numberOfBracketsAsInt)
// for (index, _) in chunks.enumerated() {
// if randomize {
// chunks[index].shuffle()
// } else if index % 2 != 0 {
// chunks[index].reverse()
// }
//
// print("Equipes \(chunks[index].map { $0.initialRank })")
// for (jIndex, _) in chunks[index].enumerated() {
// print("Position \(index+1) Poule \(orderedBrackets[jIndex].index)")
// chunks[index][jIndex].bracketPosition = orderedBrackets[jIndex].index
// chunks[index][jIndex].bracketPositions = [Int(index + 1)]
// }
// }
let max = groupStages.map { $0.size }.reduce(0,+)
var chunks = teams().filter { $0.wildCardBracket == false }.suffix(max).chunked(into: numberOfBracketsAsInt)
for (index, _) in chunks.enumerated() {
if randomize {
chunks[index].shuffle()
} else if index % 2 != 0 {
chunks[index].reverse()
}
print("Equipes \(chunks[index].map { $0.weight })")
for (jIndex, _) in chunks[index].enumerated() {
print("Position \(index+1) Poule \(groupStages[jIndex].index)")
chunks[index][jIndex].groupStage = groupStages[jIndex].id
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>) {
let team = TeamRegistration(tournament: id, registrationDate: Date())
team.tournamentCategory = tournamentCategory
team.setWeight(from: Array(players))
try? DataStore.shared.teamRegistrations.addOrUpdate(instance: team)
players.forEach { player in
player.teamRegistration = team.id
}
try? DataStore.shared.playerRegistrations.append(contentOfs: players)
try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
}
var teamSortingType: TeamSortingType {
@ -350,7 +436,12 @@ class Tournament : ModelObject, Storable {
TournamentCategory(rawValue: federalCategory) ?? .men
}
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
}
}
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 {

@ -20,3 +20,20 @@ extension Sequence {
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 {
trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
var canonicalVersionWithPunctuation: String {
trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
}
extension String {

@ -49,3 +49,42 @@ extension URL {
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 {
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? {
if let importingDate = SourceFile.mostRecentDateAvailable {
for source in SourceFile.allCases {

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

@ -7,6 +7,97 @@
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 {
case dames = "DAMES"
case messieurs = "MESSIEURS"

@ -16,18 +16,18 @@ struct Line: Identifiable {
let rawValue: String
let data: [String?]
func updatePlayer(player: Player) {
player.clubName = data[11]
player.ligue = data[9]
player.country = data[4]
player.license = data[5]
player.inscriptionRank = rank
player.currentRank = rank
player.tournamentCount = Int16(tournamentCount)
player.points = points ?? 0
player.codeClub = data[10]
player.assimilation = assimilation
}
// func updatePlayer(player: Player) {
// player.clubName = data[11]
// player.ligue = data[9]
// player.country = data[4]
// player.license = data[5]
// player.inscriptionRank = rank
// player.currentRank = rank
// player.tournamentCount = Int16(tournamentCount)
// player.points = points ?? 0
// player.codeClub = data[10]
// player.assimilation = assimilation
// }
var points: Double? {
if let pointsValue {
@ -55,9 +55,9 @@ struct Line: Identifiable {
data[1] ?? nil
}
var rank: Int64 {
var rank: Int {
if let rankValue {
return Int64(rankValue) ?? 0
return Int(rankValue) ?? 0
}
return 0
}
@ -83,7 +83,7 @@ struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
self.url = url
self.seperator = seperator
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? {

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

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

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

@ -80,7 +80,7 @@ struct EventCreationView: View {
}
try? dataStore.events.addOrUpdate(instance: event)
}
try? dataStore.tournaments.append(contentOfs: tournaments)
try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
dismiss()
}
.clipShape(Capsule())
@ -115,12 +115,6 @@ struct EventCreationView: View {
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
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)
}
}

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

@ -56,6 +56,7 @@ struct MainView: View {
.environmentObject(dataStore)
.task {
await self._checkSourceFileAvailability()
await self._downloadPreviousDate()
}
.refreshable {
Task {
@ -113,7 +114,7 @@ struct MainView: View {
print("check files on internet")
print("check if any files on internet are more recent than here")
checkingFiles = true
await fetchData()
await SourceFileManager.shared.fetchData()
checkingFilesAttempt += 1
checkingFiles = false
@ -130,6 +131,8 @@ struct MainView: View {
await _calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate)
}
importingFiles = false
_downloadPreviousDate()
}
}
@ -138,55 +141,11 @@ struct MainView: View {
lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: mostRecentDateAvailable, man: false)
}
private 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())
}
}
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)
}
}
private func _downloadPreviousDate() {
Task {
await SourceFileManager.shared.getAllFiles()
}
}
}
fileprivate extension View {

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

@ -8,11 +8,292 @@
import SwiftUI
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 {
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 {
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 {
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description:
Text("Vous n'avez encore aucune équipe dans votre liste d'attente.")
)
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez encore aucune équipe dans votre liste d'attente."))
}
// if let mostRecentDate, let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.isOver == false {

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

@ -8,11 +8,87 @@
import SwiftUI
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 {
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 {
UpdateSourceRankDateView()
UpdateSourceRankDateView(currentRankSourceDate: .constant(Date()), confirmUpdateRank: .constant(true), tournament: Tournament.mock())
}

@ -18,16 +18,36 @@ struct InscriptionManagerView: View {
var tournament: Tournament
@State private var searchField: String = ""
@State private var presentSearch: Bool = false
@State private var presentPlayerSearch: Bool = false
@State private var presentPlayerCreation: Bool = false
@State private var presentImportView: Bool = false
@State private var createdPlayers: Set<PlayerRegistration> = Set()
@State private var testCreatedPlayers: Set<String> = Set()
@State private var editedTeam: TeamRegistration?
@State private var pasteString: String?
@State private var currentRankSourceDate: Date?
@State private var confirmUpdateRank = false
@State private var updatingRank = false
let slideToDeleteTip = SlideToDeleteTip()
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? {
let text = pasteField.canonicalVersion
@ -73,6 +93,7 @@ struct InscriptionManagerView: View {
fetchPlayers.first(where: { id == $0.license })
}.forEach { player in
let player = PlayerRegistration(importedPlayer: player)
player.setWeight(in: tournament)
currentSelection.insert(player)
}
@ -116,8 +137,14 @@ struct InscriptionManagerView: View {
}
if editedTeam == nil {
RowButtonView(title: "Ajouter l'équipe") {
_createTeam()
if testCreatedPlayers.isEmpty {
RowButtonView(title: "Bloquer une place") {
_createTeam()
}
} else {
RowButtonView(title: "Ajouter l'équipe") {
_createTeam()
}
}
} else {
RowButtonView(title: "Modifier l'équipe") {
@ -126,17 +153,7 @@ struct InscriptionManagerView: View {
}
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 {
Text(pasteString)
} footer: {
@ -152,12 +169,29 @@ struct InscriptionManagerView: View {
}
}
Section {
ForEach(fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) })) { player in
ImportedPlayerView(player: player).tag(player.license!)
if fetchPlayers.isEmpty {
ContentUnavailableView {
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 {
List {
Section {
if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false {
TipView(inscriptionManagerWomanRankTip)
.tipStyle(tint: nil)
rankingDateSourcePickerView(showDateInLabel: true)
let duplicates = tournament.duplicates()
DisclosureGroup {
if duplicates.isEmpty == false {
ForEach(duplicates) { player in
PlayerView(player: player)
}
}
} label: {
LabeledContent {
Text(duplicates.count.formatted())
} label: {
Text("Doublons")
}
}
} header: {
Text("Informations")
}
if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false {
Section {
TipView(inscriptionManagerWomanRankTip)
.tipStyle(tint: nil)
}
}
Section {
TipView(slideToDeleteTip)
.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 {
TeamRowView(team: team)
} header: {
HStack {
Text("Équipe")
Text("Équipe " + team.formattedSeed(in: unfilteredTeams))
Spacer()
Text(team.computedRank().formatted())
Text(team.weight.formatted())
}
} footer: {
HStack {
@ -217,7 +299,7 @@ struct InscriptionManagerView: View {
Menu {
Button("Éditer") {
editedTeam = team
team.players().forEach { player in
team.unsortedPlayers().forEach { player in
createdPlayers.insert(player)
testCreatedPlayers.insert(player.id)
}
@ -229,14 +311,14 @@ struct InscriptionManagerView: View {
LabelDelete()
}
} label: {
LabelOptions()
LabelOptions().labelStyle(.titleOnly)
}
}
}
.headerProminence(.increased)
}
}
.searchable(text: $searchField)
.searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites"))
}
var body: some View {
@ -244,7 +326,7 @@ struct InscriptionManagerView: View {
_managementView()
if testCreatedPlayers.isEmpty == false || pasteString != nil || editedTeam != nil {
_buildingTeamView()
} else if tournament.teams().isEmpty {
} else if tournament.unsortedTeams().isEmpty {
InscriptionTipsView()
} else {
_teamRegisteredView()
@ -255,6 +337,7 @@ struct InscriptionManagerView: View {
SelectablePlayerListView(allowSelection: -1, filterOption: _filterOption()) { players in
players.forEach { player in
let newPlayer = PlayerRegistration(importedPlayer: player)
newPlayer.setWeight(in: tournament)
createdPlayers.insert(newPlayer)
testCreatedPlayers.insert(newPlayer.id)
}
@ -267,6 +350,23 @@ struct InscriptionManagerView: View {
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 {
if testCreatedPlayers.isEmpty == false {
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)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Inscription")
.navigationTitle("Inscriptions")
.navigationBarTitleDisplayMode(.inline)
}
@ -303,7 +415,7 @@ struct InscriptionManagerView: View {
guard let first = strings.first else { return }
Task {
await MainActor.run() {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: nil)
// fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: tournament.rankSourceDate)
pasteString = first
}
}
@ -329,6 +441,35 @@ struct InscriptionManagerView: View {
.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 {
switch tournament.tournamentCategory {
case .men:

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

Loading…
Cancel
Save