From f2f6e88d8a52193a1723a0380a0d34d4e7e49a96 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 12 Apr 2025 09:49:35 +0200 Subject: [PATCH] fix and clean up --- PadelClub.xcodeproj/project.pbxproj | 32 +++- PadelClub/Data/CustomUser.swift | 6 +- PadelClub/Data/Gen/BaseTeamRegistration.swift | 2 +- PadelClub/Data/Gen/BaseTournament.swift | 44 ++--- PadelClub/Data/Gen/Tournament.json | 29 +-- PadelClub/Data/Tournament.swift | 4 - PadelClub/Utils/Network/RefundService.swift | 5 + .../Network/StripeValidationService.swift | 68 +++++++ .../XlsToCsvService.swift} | 62 +++--- .../Views/Navigation/Umpire/UmpireView.swift | 33 ++-- .../Views/Tournament/FileImportView.swift | 2 +- .../Screen/Components/RefundResultsView.swift | 117 +++++++++++ .../Components/TournamentStatusView.swift | 181 +++++++++++------- .../Screen/RegistrationSetupView.swift | 63 +----- .../Views/Tournament/TournamentInitView.swift | 27 +++ 15 files changed, 449 insertions(+), 226 deletions(-) create mode 100644 PadelClub/Utils/Network/StripeValidationService.swift rename PadelClub/Utils/{CloudConvert.swift => Network/XlsToCsvService.swift} (61%) create mode 100644 PadelClub/Views/Tournament/Screen/Components/RefundResultsView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 46cbbba..22dbacd 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -423,7 +423,6 @@ FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; }; FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; - FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; }; FF4CBFEC2C996C0600151637 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; }; @@ -735,7 +734,6 @@ FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; }; FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; - FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; }; FF70FB6B2C90584900129CC2 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; }; @@ -910,7 +908,6 @@ FFA252B62CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; }; FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; }; FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; - FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; FFB378342D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; }; FFB378352D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; }; @@ -979,6 +976,15 @@ FFE8B5BB2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; }; FFE8B5BC2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; }; FFE8B5BD2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; }; + FFE8B5BF2DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; }; + FFE8B5C02DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; }; + FFE8B5C12DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; }; + FFE8B5C72DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; }; + FFE8B5C82DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; }; + FFE8B5C92DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; }; + FFE8B5CB2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; }; + FFE8B5CC2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; }; + FFE8B5CD2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; }; FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; @@ -1366,7 +1372,6 @@ FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingContainerView.swift; sourceTree = ""; }; FFA252B42CDD2C630074E63F /* OngoingDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingDestination.swift; sourceTree = ""; }; FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = ""; }; - FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = ""; }; FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = ""; }; FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatGuideView.swift; sourceTree = ""; }; @@ -1419,6 +1424,9 @@ FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineWaitingListFaqSheetView.swift; sourceTree = ""; }; FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentInfoSheetView.swift; sourceTree = ""; }; FFE8B5BA2DA9896800BDE966 /* RefundService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefundService.swift; sourceTree = ""; }; + FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefundResultsView.swift; sourceTree = ""; }; + FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeValidationService.swift; sourceTree = ""; }; + FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XlsToCsvService.swift; sourceTree = ""; }; FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonView.swift; sourceTree = ""; }; FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = ""; }; FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -2056,6 +2064,8 @@ FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */, FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */, FFE8B5BA2DA9896800BDE966 /* RefundService.swift */, + FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */, + FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */, ); path = Network; sourceTree = ""; @@ -2075,6 +2085,7 @@ FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */, FF4623CA2D1340D200CB57B5 /* TournamentCategorySettingsView.swift */, FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */, + FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */, FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */, ); path = Components; @@ -2242,7 +2253,6 @@ children = ( FF6EC9072B947A1E00EA7F5A /* Network */, FF8E1CE52C006E0200184680 /* Alphabet.swift */, - FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */, FF92680A2BCEE3E10080F940 /* ContactManager.swift */, FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */, FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */, @@ -2634,6 +2644,7 @@ FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */, FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */, FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */, + FFE8B5C12DAA325400BDE966 /* RefundResultsView.swift in Sources */, FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */, FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */, FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */, @@ -2740,6 +2751,7 @@ FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */, FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF558C632C6CDD020071F9AE /* UnderlineView.swift in Sources */, + FFE8B5CD2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */, FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */, FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */, FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */, @@ -2802,7 +2814,6 @@ FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */, FF1F4B6D2BF9E60B000B4573 /* TournamentBuildView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, - FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FFB39B352D8E8B05008E0C89 /* MatchSpot.swift in Sources */, @@ -2851,6 +2862,7 @@ FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */, FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */, FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */, + FFE8B5C82DAA390900BDE966 /* StripeValidationService.swift in Sources */, FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */, C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */, FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */, @@ -3056,6 +3068,7 @@ FF4CBFAD2C996C0600151637 /* RoundScheduleEditorView.swift in Sources */, FF4CBFAE2C996C0600151637 /* EventListView.swift in Sources */, FF4CBFAF2C996C0600151637 /* TournamentConfiguratorView.swift in Sources */, + FFE8B5C72DAA390900BDE966 /* StripeValidationService.swift in Sources */, FF4CBFB02C996C0600151637 /* ClubImportView.swift in Sources */, FF4CBFB12C996C0600151637 /* UnderlineView.swift in Sources */, FF4CBFB22C996C0600151637 /* MatchScheduler.swift in Sources */, @@ -3121,7 +3134,6 @@ FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */, FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */, FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */, - FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */, FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */, FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */, FFB39B342D8E8B05008E0C89 /* MatchSpot.swift in Sources */, @@ -3130,6 +3142,7 @@ FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */, FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */, FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */, + FFE8B5CC2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */, FF3A73F52D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, FF4CBFF02C996C0600151637 /* URLs.swift in Sources */, FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */, @@ -3194,6 +3207,7 @@ FF4CC0212C996C0600151637 /* GroupStageScheduleEditorView.swift in Sources */, FF4CC0222C996C0600151637 /* EventView.swift in Sources */, FF4CC0232C996C0600151637 /* LoginView.swift in Sources */, + FFE8B5BF2DAA325400BDE966 /* RefundResultsView.swift in Sources */, FF4CC0242C996C0600151637 /* Court.swift in Sources */, FF4CC0252C996C0600151637 /* Labels.swift in Sources */, FF4CC0262C996C0600151637 /* CopyPasteButtonView.swift in Sources */, @@ -3353,6 +3367,7 @@ FF70FB2C2C90584900129CC2 /* RoundScheduleEditorView.swift in Sources */, FF70FB2D2C90584900129CC2 /* EventListView.swift in Sources */, FF70FB2E2C90584900129CC2 /* TournamentConfiguratorView.swift in Sources */, + FFE8B5C92DAA390900BDE966 /* StripeValidationService.swift in Sources */, FF70FB2F2C90584900129CC2 /* ClubImportView.swift in Sources */, FF70FB302C90584900129CC2 /* UnderlineView.swift in Sources */, FF70FB312C90584900129CC2 /* MatchScheduler.swift in Sources */, @@ -3418,7 +3433,6 @@ FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */, FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */, FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */, - FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */, FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */, FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */, FFB39B362D8E8B05008E0C89 /* MatchSpot.swift in Sources */, @@ -3427,6 +3441,7 @@ FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */, FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */, FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */, + FFE8B5CB2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */, FF3A73F42D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */, FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */, @@ -3491,6 +3506,7 @@ FF70FBA02C90584900129CC2 /* GroupStageScheduleEditorView.swift in Sources */, FF70FBA12C90584900129CC2 /* EventView.swift in Sources */, FF70FBA22C90584900129CC2 /* LoginView.swift in Sources */, + FFE8B5C02DAA325400BDE966 /* RefundResultsView.swift in Sources */, FF70FBA32C90584900129CC2 /* Court.swift in Sources */, FF70FBA42C90584900129CC2 /* Labels.swift in Sources */, FF70FBA52C90584900129CC2 /* CopyPasteButtonView.swift in Sources */, diff --git a/PadelClub/Data/CustomUser.swift b/PadelClub/Data/CustomUser.swift index 1184481..6e1d48a 100644 --- a/PadelClub/Data/CustomUser.swift +++ b/PadelClub/Data/CustomUser.swift @@ -64,12 +64,12 @@ enum RegistrationPaymentMode: Int, Codable { } } - func sample(entryFee: Double) -> String? { + func sample(entryFee: Double) -> String { if let fee = self.fee() { let feeAmount = entryFee * fee - return String(format: "%.2f€", feeAmount) + return String(format: "%.2f", feeAmount) } else { - return nil + return "0" } } diff --git a/PadelClub/Data/Gen/BaseTeamRegistration.swift b/PadelClub/Data/Gen/BaseTeamRegistration.swift index 2d0f596..8710c5e 100644 --- a/PadelClub/Data/Gen/BaseTeamRegistration.swift +++ b/PadelClub/Data/Gen/BaseTeamRegistration.swift @@ -196,4 +196,4 @@ class BaseTeamRegistration: SyncedModelObject, SyncedStorable { ] } -} +} \ No newline at end of file diff --git a/PadelClub/Data/Gen/BaseTournament.swift b/PadelClub/Data/Gen/BaseTournament.swift index c55763a..cb2d489 100644 --- a/PadelClub/Data/Gen/BaseTournament.swift +++ b/PadelClub/Data/Gen/BaseTournament.swift @@ -23,20 +23,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable { var roundFormat: MatchFormat? = nil var loserRoundFormat: MatchFormat? = nil var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake - var groupStageCount: Int = 0 + var groupStageCount: Int = 4 var rankSourceDate: Date? = nil - var dayDuration: Int = 0 - var teamCount: Int = 0 + var dayDuration: Int = 1 + var teamCount: Int = 24 var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate var federalCategory: TournamentCategory = TournamentCategory.men - var federalLevelCategory: TournamentLevel = TournamentLevel.unlisted - var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted + var federalLevelCategory: TournamentLevel = TournamentLevel.p100 + var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior var closedRegistrationDate: Date? = nil var groupStageAdditionalQualified: Int = 0 var courtCount: Int = 2 var prioritizeClubMembers: Bool = false - var qualifiedPerGroupStage: Int = 0 - var teamsPerGroupStage: Int = 0 + var qualifiedPerGroupStage: Int = 1 + var teamsPerGroupStage: Int = 4 var entryFee: Double? = nil var payment: TournamentPayment? = nil var additionalEstimationDuration: Int = 0 @@ -92,20 +92,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable { roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake, - groupStageCount: Int = 0, + groupStageCount: Int = 4, rankSourceDate: Date? = nil, - dayDuration: Int = 0, - teamCount: Int = 0, + dayDuration: Int = 1, + teamCount: Int = 24, teamSorting: TeamSortingType = TeamSortingType.inscriptionDate, federalCategory: TournamentCategory = TournamentCategory.men, - federalLevelCategory: TournamentLevel = TournamentLevel.unlisted, - federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted, + federalLevelCategory: TournamentLevel = TournamentLevel.p100, + federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, - qualifiedPerGroupStage: Int = 0, - teamsPerGroupStage: Int = 0, + qualifiedPerGroupStage: Int = 1, + teamsPerGroupStage: Int = 4, entryFee: Double? = nil, payment: TournamentPayment? = nil, additionalEstimationDuration: Int = 0, @@ -374,20 +374,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable { self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake - self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 0 + self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 4 self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil - self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 0 - self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 0 + self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 1 + self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 24 self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men - self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.unlisted - self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.unlisted + self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.p100 + self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.senior self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0 self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2 self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false - self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 0 - self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 0 + self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 1 + self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 4 self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil self.payment = try Self._decodePayment(container: container) self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0 @@ -587,4 +587,4 @@ class BaseTournament: SyncedModelObject, SyncedStorable { ] } -} \ No newline at end of file +} diff --git a/PadelClub/Data/Gen/Tournament.json b/PadelClub/Data/Gen/Tournament.json index d287efe..ce4f01c 100644 --- a/PadelClub/Data/Gen/Tournament.json +++ b/PadelClub/Data/Gen/Tournament.json @@ -66,7 +66,8 @@ }, { "name": "groupStageCount", - "type": "Int" + "type": "Int", + "defaultValue": "4" }, { "name": "rankSourceDate", @@ -75,11 +76,13 @@ }, { "name": "dayDuration", - "type": "Int" + "type": "Int", + "defaultValue": "1" }, { "name": "teamCount", - "type": "Int" + "type": "Int", + "defaultValue": "24" }, { "name": "teamSorting", @@ -94,12 +97,12 @@ { "name": "federalLevelCategory", "type": "TournamentLevel", - "defaultValue": "TournamentLevel.unlisted" + "defaultValue": "TournamentLevel.p100" }, { "name": "federalAgeCategory", "type": "FederalTournamentAge", - "defaultValue": "FederalTournamentAge.unlisted" + "defaultValue": "FederalTournamentAge.senior" }, { "name": "closedRegistrationDate", @@ -108,7 +111,8 @@ }, { "name": "groupStageAdditionalQualified", - "type": "Int" + "type": "Int", + "defaultValue": "0" }, { "name": "courtCount", @@ -117,15 +121,18 @@ }, { "name": "prioritizeClubMembers", - "type": "Bool" + "type": "Bool", + "defaultValue": "false" }, { "name": "qualifiedPerGroupStage", - "type": "Int" + "type": "Int", + "defaultValue": "1" }, { "name": "teamsPerGroupStage", - "type": "Int" + "type": "Int", + "defaultValue": "4" }, { "name": "entryFee", @@ -255,12 +262,12 @@ { "name": "minimumPlayerPerTeam", "type": "Int", - "defaultValue": 2 + "defaultValue": "2" }, { "name": "maximumPlayerPerTeam", "type": "Int", - "defaultValue": 2 + "defaultValue": "2" }, { "name": "information", diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 8bfe7c4..7dcff20 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -2382,10 +2382,6 @@ defer { func paidOnlineTeams() -> [TeamRegistration] { unsortedTeams().filter({ $0.hasPaidOnline() }) } - - func refundTeams() { - - } func shouldWarnOnlineRegistrationUpdates() -> Bool { enableOnlineRegistration && onlineTeams().isEmpty == false && hasEnded() == false && hasStarted() == false diff --git a/PadelClub/Utils/Network/RefundService.swift b/PadelClub/Utils/Network/RefundService.swift index 721e15c..20eb00f 100644 --- a/PadelClub/Utils/Network/RefundService.swift +++ b/PadelClub/Utils/Network/RefundService.swift @@ -42,3 +42,8 @@ enum RefundError: Error { case unauthorized case unknown } + +struct RefundResult { + let team: TeamRegistration + let response: Result +} diff --git a/PadelClub/Utils/Network/StripeValidationService.swift b/PadelClub/Utils/Network/StripeValidationService.swift new file mode 100644 index 0000000..dce0266 --- /dev/null +++ b/PadelClub/Utils/Network/StripeValidationService.swift @@ -0,0 +1,68 @@ +// +// StripeValidationService.swift +// PadelClub +// +// Created by razmig on 12/04/2025. +// + +import Foundation +import LeStorage + +class StripeValidationService { + + static func validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse { + let service = try StoreCenter.main.service() + var urlRequest = try service._baseRequest(servicePath: "validate-stripe-account/", method: .post, requiresToken: true) + + let body = ["account_id": accountID] + urlRequest.httpBody = try JSONEncoder().encode(body) + + do { + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw ValidationError.invalidResponse + } + switch httpResponse.statusCode { + case 200...299: + let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return decodedResponse + case 400: + // Handle bad request + let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return errorResponse + case 403: + // Handle permission error + let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return errorResponse + default: + throw ValidationError.invalidResponse + } + } catch let error as ValidationError { + throw error + } catch { + throw ValidationError.networkError(error) + } + } +} + +struct ValidationResponse: Codable { + let valid: Bool + let account: AccountDetails? + let error: String? +} + +struct AccountDetails: Codable { + let id: String + + enum CodingKeys: String, CodingKey { + case id + } +} + +enum ValidationError: Error { + case invalidResponse + case networkError(Error) + case invalidData +} + diff --git a/PadelClub/Utils/CloudConvert.swift b/PadelClub/Utils/Network/XlsToCsvService.swift similarity index 61% rename from PadelClub/Utils/CloudConvert.swift rename to PadelClub/Utils/Network/XlsToCsvService.swift index ab1569a..8d55787 100644 --- a/PadelClub/Utils/CloudConvert.swift +++ b/PadelClub/Utils/Network/XlsToCsvService.swift @@ -1,44 +1,18 @@ // -// CloudConvert.swift -// Padel Tournament +// XlsToCsvService.swift +// PadelClub // -// Created by Razmig Sarkissian on 14/09/2023. +// Created by razmig on 12/04/2025. // import Foundation +import LeStorage -class CloudConvert { +class XlsToCsvService { - enum CloudConvertionError: LocalizedError { - case unknownError - case serviceError(ErrorResponse) - case urlNotFound(String) - - var errorDescription: String? { - switch self { - case .unknownError: - return "Erreur" - case .serviceError(let errorResponse): - return errorResponse.error - case .urlNotFound(let url): - return "L'URL [\(url)] n'est pas valide" - } - } - } - - static let manager = CloudConvert() - - func uploadFile(_ url: URL) async throws -> String { - return try await createJob(url) - } - - func createJob(_ url: URL) async throws -> String { - let apiPath = "https://\(URLs.activationHost.rawValue)/utils/xls-to-csv/" - guard let taskURL = URL(string: apiPath) else { - throw CloudConvertionError.urlNotFound(apiPath) - } - var request: URLRequest = URLRequest(url: taskURL) - request.httpMethod = "POST" + static func exportToCsv(url: URL) async throws -> String { + let service = try StoreCenter.main.service() + var request = try service._baseRequest(servicePath: "xls-to-csv/", method: .post, requiresToken: true) // Create the boundary string for multipart/form-data let boundary = UUID().uuidString @@ -81,13 +55,29 @@ class CloudConvert { return responseString } else { let error = ErrorResponse(code: 1, status: "Encodage", error: "Encodage des données de classement invalide") - throw CloudConvertionError.serviceError(error) + throw ConvertionError.serviceError(error) } } } -// MARK: - ErrorResponse struct ErrorResponse: Decodable { let code: Int let status, error: String } + +enum ConvertionError: LocalizedError { + case unknownError + case serviceError(ErrorResponse) + case urlNotFound(String) + + var errorDescription: String? { + switch self { + case .unknownError: + return "Erreur" + case .serviceError(let errorResponse): + return errorResponse.error + case .urlNotFound(let url): + return "L'URL [\(url)] n'est pas valide" + } + } +} diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index ef440e7..0cf1522 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -142,25 +142,26 @@ struct UmpireView: View { // } // } // - _customUmpireView() - - Section { - @Bindable var user = dataStore.user - if dataStore.user.hideUmpireMail, dataStore.user.hideUmpirePhone { - Text("Attention, les emails envoyés automatiquement au regard des inscriptions en ligne ne contiendront aucun moyen de vous contacter.").foregroundStyle(.logoRed) - } + if StoreCenter.main.isAuthenticated { + _customUmpireView() - Toggle(isOn: $user.hideUmpireMail) { - Text("Masquer l'email") - } - Toggle(isOn: $user.hideUmpirePhone) { - Text("Masquer le téléphone") + Section { + @Bindable var user = dataStore.user + if dataStore.user.hideUmpireMail, dataStore.user.hideUmpirePhone { + Text("Attention, les emails envoyés automatiquement au regard des inscriptions en ligne ne contiendront aucun moyen de vous contacter.").foregroundStyle(.logoRed) + } + + Toggle(isOn: $user.hideUmpireMail) { + Text("Masquer l'email") + } + Toggle(isOn: $user.hideUmpirePhone) { + Text("Masquer le téléphone") + } + + } footer: { + Text("Ces informations ne seront pas affichées sur la page d'information des tournois sur Padel Club et dans les emails envoyés automatiquement au regard des inscriptions en lignes.") } - - } footer: { - Text("Ces informations ne seront pas affichées sur la page d'information des tournois sur Padel Club et dans les emails envoyés automatiquement au regard des inscriptions en lignes.") } - Section { @Bindable var user = dataStore.user diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 4e2353d..2646da5 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -421,7 +421,7 @@ struct FileImportView: View { do { if selectedFile.lastPathComponent.hasSuffix("xls") || selectedFile.lastPathComponent.hasSuffix("xlsx") { convertingFile = true - fileContent = try await CloudConvert.manager.uploadFile(selectedFile) + fileContent = try await XlsToCsvService.exportToCsv(url: selectedFile) convertingFile = false } else { fileContent = try String(contentsOf: selectedFile) diff --git a/PadelClub/Views/Tournament/Screen/Components/RefundResultsView.swift b/PadelClub/Views/Tournament/Screen/Components/RefundResultsView.swift new file mode 100644 index 0000000..f3e536a --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/RefundResultsView.swift @@ -0,0 +1,117 @@ +// +// RefundResultsView.swift +// PadelClub +// +// Created by razmig on 12/04/2025. +// + +import SwiftUI +import LeStorage + +struct RefundResultsView: View { + @Binding var results: [RefundResult] + let tournament: Tournament + @Environment(\.dismiss) private var dismiss + @State private var processingRetry: Set = [] + + private var hasErrors: Bool { + failedRefundResult.isEmpty == false + } + + private var failedRefundResult: [RefundResult] { + results.filter { result in + if case .failure = result.response { + return true + } + return false + } + } + + private var errorCount: Int { + failedRefundResult.count + } + + var body: some View { + List { + + if hasErrors { + RowButtonView("Réessayer tous les remboursements échoués", role: .destructive) { + // Implement retry logic for failed refunds + await _retryFailedRefund() + } + } + + let sorted = results.sorted(by: \.team.weight) + + ForEach(sorted, id: \.team.id) { result in + LabeledContent { + if processingRetry.contains(result.team.id) { + ProgressView() + } else { + Button { + Task { + await _retryResult(result: result) + } + } label: { + Label("refaire", systemImage: "arrow.2.circlepath.circle").labelStyle(.iconOnly) + } + .buttonStyle(.borderedProminent) + } + } label: { + Text(result.team.teamLabel(twoLines: true)) + switch result.response { + case .success(let response): + Text(response.message) + .foregroundColor(response.success ? .green : .logoRed) + case .failure(let error): + Text(error.localizedDescription) + .foregroundColor(.logoRed) + } + } + } + } + .navigationTitle("Résultats des remboursements") + .toolbarBackground(.visible, for: .bottomBar, .navigationBar) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + if hasErrors { + Label("\(errorCount) erreur\(errorCount.pluralSuffix)", systemImage: "exclamationmark.triangle") + .foregroundColor(.red) + } else { + Label("Remboursements effectués", systemImage: "checkmark.circle.fill") + .foregroundColor(.green) + } + } + } + } + + private func _retryResult(result: RefundResult) async { + processingRetry.insert(result.team.id) + do { + let response = try await RefundService.processRefund(teamRegistrationId: result.team.id) + results.removeAll(where: { $0.team.id == result.team.id }) + results.append(RefundResult(team: result.team, response: .success(response))) + } catch { + results.removeAll(where: { $0.team.id == result.team.id }) + results.append(RefundResult(team: result.team, response: .failure(error))) + } + processingRetry.remove(result.team.id) + } + + private func _retryFailedRefund() async { + let failed = failedRefundResult + await failed.concurrentForEach { result in + results.removeAll(where: { $0.team.id == result.team.id }) + do { + let response = try await RefundService.processRefund(teamRegistrationId: result.team.id) + if let players = response.players { + tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) + } + results.append(RefundResult(team: result.team, response: .success(response))) + } catch { + results.append(RefundResult(team: result.team, response: .failure(error))) + } + } + } +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift index c32c117..85ca1c7 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -13,10 +13,12 @@ struct TournamentStatusView: View { @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @EnvironmentObject var dataStore: DataStore @Bindable var tournament: Tournament + @State private var refundResults: [RefundResult] = [] + var eventDismiss: Bool = false var body: some View { - Form { + List { #if DEBUG RowButtonView("debug: Un-delete le tournoi") { tournament.endDate = nil @@ -44,13 +46,28 @@ struct TournamentStatusView: View { let paidOnlineTeams = tournament.paidOnlineTeams() + let onlineTeams = tournament.onlineTeams() + + if onlineTeams.isEmpty == false { + Section { + LabeledContent { + Text(onlineTeams.count.formatted()) + } label: { + Text("Équipes inscrites ligne") + } + + Text("Vous avez des équipes inscrites en ligne, en effacant ou supprimant votre tournoi, Padel Club saura prévenir les équipes inscrites en ligne automatiquement.") + .foregroundStyle(.logoRed) + } + } + Section { RowButtonView("Supprimer le tournoi", role: .destructive) { if tournament.payment == nil { let event = tournament.eventObject() let isLastTournament = event?.tournaments.count == 1 - if tournament.onlineTeams().isEmpty == false { + if onlineTeams.isEmpty == false { tournament.isDeleted = true dataStore.tournaments.addOrUpdate(instance: tournament) } else { @@ -88,82 +105,103 @@ struct TournamentStatusView: View { } .disabled(paidOnlineTeams.isEmpty == false) - if tournament.hasEnded() == false && tournament.isCanceled == false { - Section { + Section { + + @Bindable var bindableTournament: Tournament = tournament + + if paidOnlineTeams.isEmpty == false { + LabeledContent { + Text(paidOnlineTeams.count.formatted()) + } label: { + Text("Équipes ayant payé en ligne") + } - @Bindable var bindableTournament: Tournament = tournament - if paidOnlineTeams.isEmpty == false { - LabeledContent { - Text(paidOnlineTeams.count.formatted()) - } label: { - Text("Équipes ayant payé en ligne") - } - - - Toggle(isOn: $bindableTournament.enableOnlinePaymentRefund) { - Text("Remboursement possible") - } + Toggle(isOn: $bindableTournament.enableOnlinePaymentRefund) { + Text("Remboursement possible") + } - if tournament.enableOnlinePaymentRefund { - if let refundDateLimit = tournament.refundDateLimit { - LabeledContent { - Text(refundDateLimit.formatted()) - } label: { - Text("Date limite") - } - - if refundDateLimit.isEarlierThan(Date()) == false { - Text("Le remboursement est toujours possible") - } else { - Text("La date limite a été dépassé") - FooterButtonView("Retirer la date limite ?", role: .destructive) { - tournament.refundDateLimit = nil - _save() - } + if tournament.enableOnlinePaymentRefund { + if let refundDateLimit = tournament.refundDateLimit { + LabeledContent { + Text(refundDateLimit.formatted()) + } label: { + Text("Date limite") + } + + if refundDateLimit.isEarlierThan(Date()) == false { + Text("Le remboursement est toujours possible") + } else { + Text("La date limite a été dépassé") + FooterButtonView("Retirer la date limite ?", role: .destructive) { + tournament.refundDateLimit = nil + _save() } } } - - if tournament.enableOnlinePaymentRefund { - if let refundDateLimit = tournament.refundDateLimit { - if refundDateLimit.isEarlierThan(Date()) == false { - Text("\(paidOnlineTeams.count) équipe\(paidOnlineTeams.count.pluralSuffix) seront remboursée\(paidOnlineTeams.count.pluralSuffix)") - } else { - Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées car la date limite a été dépassé") - } + } + + if tournament.enableOnlinePaymentRefund { + if let refundDateLimit = tournament.refundDateLimit { + if refundDateLimit.isEarlierThan(Date()) == false { + Text("\(paidOnlineTeams.count) équipe\(paidOnlineTeams.count.pluralSuffix) seront remboursée\(paidOnlineTeams.count.pluralSuffix)") } else { - Text("Les équipes ayant payé en ligne seront remboursées") + Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées car la date limite a été dépassé") } } else { - Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées vous n'avez pas autorisé le remboursement.") + Text("Les équipes ayant payé en ligne seront remboursées") } - - Text("Si vous annulez ce tournoi vous pouvez toujours gérer les remboursements au cas par cas dans la vue gestion des inscriptions du tournoi.") + } else { + Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées vous n'avez pas autorisé le remboursement.") } - - RowButtonView("Annuler le tournoi", role: .destructive) { - if paidOnlineTeams.isEmpty == false { + Text("Si vous annulez ce tournoi vous pouvez toujours gérer les remboursements au cas par cas dans la vue gestion des inscriptions du tournoi.") + } + + if refundResults.isEmpty == false { + NavigationLink { + RefundResultsView(results: $refundResults, tournament: tournament) + } label: { + LabeledContent { + Text("Statut des remboursements") + } label: { + let errorCount = refundResults.filter { result in + if case .failure = result.response { return true } + return false + }.count - if tournament.enableOnlinePaymentRefund { - if let refundDateLimit = tournament.refundDateLimit { - if refundDateLimit.isEarlierThan(Date()) == false { - tournament.refundTeams() - } - } else { - tournament.refundTeams() + if errorCount > 0 { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.logoRed) + } else { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + } + } + } + } + + RowButtonView("Annuler le tournoi", role: .destructive) { + if paidOnlineTeams.isEmpty == false { + + if tournament.enableOnlinePaymentRefund { + if let refundDateLimit = tournament.refundDateLimit { + if refundDateLimit.isEarlierThan(Date()) == false { + await _refundTeams() } + } else { + await _refundTeams() } } - tournament.endDate = Date() - tournament.isCanceled = true - _save() - dismiss() } - } footer: { - Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé dans vos achats. Toutes les données du tournoi seront conservées. Le tournoi reste visible sur [Padel Club](\(URLs.main.rawValue))")) + tournament.endDate = Date() + tournament.isCanceled = true + _save() + dismiss() } + .disabled(tournament.hasEnded() || tournament.isCanceled) + } footer: { + Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé dans vos achats. Toutes les données du tournoi seront conservées. Le tournoi reste visible sur [Padel Club](\(URLs.main.rawValue))")) } // Section { @@ -190,11 +228,26 @@ struct TournamentStatusView: View { } } + private func _refundTeams() async { + let paidOnlineTeams = tournament.paidOnlineTeams() + var results: [RefundResult] = [] + + await paidOnlineTeams.concurrentForEach { team in + do { + let response = try await RefundService.processRefund(teamRegistrationId: team.id) + if let players = response.players { + tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) + } + results.append(RefundResult(team: team, response: .success(response))) + } catch { + results.append(RefundResult(team: team, response: .failure(error))) + } + } + + self.refundResults = results + } + private func _save() { dataStore.tournaments.addOrUpdate(instance: tournament) } } - -//#Preview { -// TournamentStatusView() -//} diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 89f1ece..628aaca 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -377,7 +377,8 @@ struct RegistrationSetupView: View { if let fee = dataStore.user.registrationPaymentMode.localizedRegistrationPaymentFee() { let entryFee = tournament.entryFee ?? 20 - Text("Cette fonction entraîne un coût supplémentaire, en effet Padel Club touchera une commission de \(fee) par paiement en ligne. Soit \(dataStore.user.registrationPaymentMode.sample(entryFee: entryFee)) centimes pour une inscription de \(entryFee)€ par exemple.").foregroundStyle(.logoRed).bold() + let sample = dataStore.user.registrationPaymentMode.sample(entryFee: entryFee) + Text("Cette fonction entraîne un coût supplémentaire, en effet Padel Club touchera une commission de \(fee) par paiement en ligne. Soit \(sample) centimes pour une inscription de \(entryFee)€ par exemple.").foregroundStyle(.logoRed).bold() } Toggle(isOn: $onlinePaymentIsMandatory) { @@ -474,7 +475,7 @@ struct RegistrationSetupView: View { Task { isValidating = true do { - let response = try await _validateStripeAccountID(accId) + let response = try await StripeValidationService.validateStripeAccountID(accId) stripeAccountId = accId stripeAccountIdIsInvalid = response.valid == false enableOnlinePayment = response.valid @@ -486,64 +487,6 @@ struct RegistrationSetupView: View { } } - private func _validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse { - let apiPath = "\(URLs.activationHost.rawValue)/utils/validate-stripe-account/" - let url = URL(string: apiPath)! - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let body = ["account_id": accountID] - request.httpBody = try JSONEncoder().encode(body) - - do { - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse else { - throw ValidationError.invalidResponse - } - switch httpResponse.statusCode { - case 200...299: - let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) - return decodedResponse - case 400: - // Handle bad request - let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) - return errorResponse - case 403: - // Handle permission error - let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) - return errorResponse - default: - throw ValidationError.invalidResponse - } - } catch let error as ValidationError { - throw error - } catch { - throw ValidationError.networkError(error) - } - } - - struct ValidationResponse: Codable { - let valid: Bool - let account: AccountDetails? - let error: String? - } - - struct AccountDetails: Codable { - let id: String - - enum CodingKeys: String, CodingKey { - case id - } - } - - enum ValidationError: Error { - case invalidResponse - case networkError(Error) - case invalidData - } - private func _hasChanged() { hasChanges = true } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index 2f36a05..4530929 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -11,9 +11,36 @@ import LeStorage struct TournamentInitView: View { @EnvironmentObject var dataStore: DataStore var tournament: Tournament + @State private var cashierStatus: Tournament.TournamentStatus? @ViewBuilder var body: some View { + + if tournament.paidOnlineTeams().isEmpty == false { + Section { + NavigationLink(value: Screen.cashier) { + let tournamentStatus = cashierStatus + LabeledContent { + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() + } + } label: { + Text(tournament.isFree() ? "Présence" : "Encaissement") + if let tournamentStatus { + Text(tournamentStatus.label).lineLimit(1) + } else { + Text(" ") + } + } + } + .task(priority: .background) { + cashierStatus = await tournament.cashierStatus() + } + } + } + Section { if let event = tournament.eventObject() {