From d1435688eb5e34ba746b79755160ca5c3f4de004 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 2 Oct 2025 07:53:55 +0200 Subject: [PATCH] fix payment stuff --- PadelClub.xcodeproj/project.pbxproj | 24 +++++ PadelClub/Utils/Network/PaymentService.swift | 41 ++++++++ PadelClub/Views/Player/PlayerDetailView.swift | 4 +- PadelClub/Views/Team/EditingTeamView.swift | 4 + .../Views/Team/PaymentRequestButton.swift | 51 ++++++++++ PadelClub/Views/Team/PaymentService.swift | 24 +++++ .../Screen/InscriptionManagerView.swift | 99 ++++++++++++++----- 7 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 PadelClub/Utils/Network/PaymentService.swift create mode 100644 PadelClub/Views/Team/PaymentRequestButton.swift create mode 100644 PadelClub/Views/Team/PaymentService.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 984b308..3383ce2 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -154,6 +154,12 @@ FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; + FF30ACED2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACEE2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACEF2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACF12E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; + FF30ACF22E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; + FF30ACF32E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; FF39B6152DC8825E004E10CE /* PadelClubData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49770202DC25A23005CD239 /* PadelClubData.framework */; }; @@ -717,6 +723,9 @@ FFA97C9E2E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; FFA97C9F2E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; FFA97CA02E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; + FFA97CA22E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; + FFA97CA32E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; + FFA97CA42E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; FFB0FF672E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; FFB0FF682E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; FFB0FF692E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; @@ -1019,6 +1028,8 @@ FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_1_1.xcdatamodel; sourceTree = ""; }; FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = ""; }; FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; + FF30ACEC2E8D700B008B6006 /* PaymentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentService.swift; sourceTree = ""; }; + FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRequestButton.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; FF39B60F2DC87FEB004E10CE /* PadelClubData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PadelClubData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1126,6 +1137,7 @@ FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FFA97C8C2E8A59D00089EA22 /* TournamentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentPickerView.swift; sourceTree = ""; }; FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingGroupStageSetupView.swift; sourceTree = ""; }; FFB0FF662E81B671009EDEAC /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; FFB0FF722E841042009EDEAC /* WeekdaySelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekdaySelectionView.swift; sourceTree = ""; }; FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = ""; }; @@ -1762,6 +1774,7 @@ FF4AB6B42B9248200002987F /* NetworkManager.swift */, FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */, FFE8B5BA2DA9896800BDE966 /* RefundService.swift */, + FF30ACEC2E8D700B008B6006 /* PaymentService.swift */, FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */, FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */, FFE8B6392DACEAEC00BDE966 /* ConfigurationService.swift */, @@ -1814,6 +1827,7 @@ FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */, FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */, + FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */, FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3922BE3625200C2E883 /* Shared */, ); @@ -1842,6 +1856,7 @@ FF1162862BD004AD000C4809 /* EditingTeamView.swift */, FF17CA562CC02FEA003C7323 /* CoachListView.swift */, FF7DCD382CC330260041110C /* TeamRestingView.swift */, + FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */, FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; @@ -2281,6 +2296,7 @@ C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */, FF17CA4F2CB9243E003C7323 /* FollowUpMatchView.swift in Sources */, + FF30ACEF2E8D700B008B6006 /* PaymentService.swift in Sources */, FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */, @@ -2315,6 +2331,7 @@ FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */, FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */, FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */, + FF30ACF22E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */, C40CD2F32C412681000DBD9A /* AppDelegate.swift in Sources */, FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */, @@ -2451,6 +2468,7 @@ FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FFE8B5B32DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, + FFA97CA32E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */, @@ -2550,6 +2568,7 @@ FFA252AF2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF4CBF6F2C996C0600151637 /* ListRowViewModifier.swift in Sources */, FF4CBF702C996C0600151637 /* PresentationContext.swift in Sources */, + FF30ACEE2E8D700B008B6006 /* PaymentService.swift in Sources */, FF4CBF722C996C0600151637 /* SwiftParser.swift in Sources */, FF4CBF732C996C0600151637 /* ChangePasswordView.swift in Sources */, FF4CBF742C996C0600151637 /* TournamentSubscriptionView.swift in Sources */, @@ -2584,6 +2603,7 @@ FF4CBF882C996C0600151637 /* TeamRowView.swift in Sources */, FF4CBF8A2C996C0600151637 /* AppDelegate.swift in Sources */, FF4CBF8C2C996C0600151637 /* EditScoreView.swift in Sources */, + FF30ACF12E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF4CBF8D2C996C0600151637 /* TournamentOrganizerView.swift in Sources */, FF4CBF8F2C996C0600151637 /* TournamentRunningView.swift in Sources */, FF4CBF902C996C0600151637 /* TournamentDatePickerView.swift in Sources */, @@ -2720,6 +2740,7 @@ FF4CC0182C996C0600151637 /* ClubHolder.swift in Sources */, FF4CC0192C996C0600151637 /* EventSettingsView.swift in Sources */, C49771E72DC25F04005CD239 /* Color+Extensions.swift in Sources */, + FFA97CA22E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, C49771E82DC25F04005CD239 /* Badge+Extensions.swift in Sources */, C49771E92DC25F04005CD239 /* SpinDrawable+Extensions.swift in Sources */, FF4CC01A2C996C0600151637 /* InscriptionInfoView.swift in Sources */, @@ -2797,6 +2818,7 @@ FFA252AD2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF70FAEE2C90584900129CC2 /* ListRowViewModifier.swift in Sources */, FF70FAEF2C90584900129CC2 /* PresentationContext.swift in Sources */, + FF30ACED2E8D700B008B6006 /* PaymentService.swift in Sources */, FF70FAF12C90584900129CC2 /* SwiftParser.swift in Sources */, FF70FAF22C90584900129CC2 /* ChangePasswordView.swift in Sources */, FF70FAF32C90584900129CC2 /* TournamentSubscriptionView.swift in Sources */, @@ -2831,6 +2853,7 @@ FF70FB072C90584900129CC2 /* TeamRowView.swift in Sources */, FF70FB092C90584900129CC2 /* AppDelegate.swift in Sources */, FF70FB0B2C90584900129CC2 /* EditScoreView.swift in Sources */, + FF30ACF32E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF70FB0C2C90584900129CC2 /* TournamentOrganizerView.swift in Sources */, FF70FB0E2C90584900129CC2 /* TournamentRunningView.swift in Sources */, FF70FB0F2C90584900129CC2 /* TournamentDatePickerView.swift in Sources */, @@ -2967,6 +2990,7 @@ FF70FB972C90584900129CC2 /* ClubHolder.swift in Sources */, FF70FB982C90584900129CC2 /* EventSettingsView.swift in Sources */, C49771E42DC25F04005CD239 /* Color+Extensions.swift in Sources */, + FFA97CA42E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, C49771E52DC25F04005CD239 /* Badge+Extensions.swift in Sources */, C49771E62DC25F04005CD239 /* SpinDrawable+Extensions.swift in Sources */, FF70FB992C90584900129CC2 /* InscriptionInfoView.swift in Sources */, diff --git a/PadelClub/Utils/Network/PaymentService.swift b/PadelClub/Utils/Network/PaymentService.swift new file mode 100644 index 0000000..1eef4db --- /dev/null +++ b/PadelClub/Utils/Network/PaymentService.swift @@ -0,0 +1,41 @@ +// +// PaymentService.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/10/2025. +// + +import Foundation +import LeStorage +import PadelClubData + +class PaymentService { + static func resendPaymentEmail(teamRegistrationId: String) async throws -> SimpleResponse { + let service = try StoreCenter.main.service() + let urlRequest = try service._baseRequest( + servicePath: "resend-payment-email/\(teamRegistrationId)/", + method: .post, + requiresToken: true + ) + + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PaymentError.requestFailed + } + + return try JSON.decoder.decode(SimpleResponse.self, from: data) + } +} + +enum PaymentError: Error { + case requestFailed + case unauthorized + case unknown +} + +struct SimpleResponse: Codable { + let success: Bool + let message: String +} diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 74fb923..457217b 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -286,7 +286,7 @@ struct PlayerDetailView: View { } LabeledContent { - TextField("Téléphone contact", text: $contactPhoneNumber) + TextField("Téléphone", text: $contactPhoneNumber) .focused($focusedField, equals: ._contactPhoneNumber) .keyboardType(.namePhonePad) .textContentType(nil) @@ -320,7 +320,7 @@ struct PlayerDetailView: View { } LabeledContent { - TextField("Email contact", text: $contactEmail) + TextField("Email", text: $contactEmail) .focused($focusedField, equals: ._contactEmail) .keyboardType(.emailAddress) .textContentType(nil) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 5b81bf2..6d10c35 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -140,6 +140,10 @@ struct EditingTeamView: View { } label: { Text("Payé en ligne") } + + if team.hasPaidOnline() == false { + PaymentRequestButton(teamRegistration: team) + } } if let refundMessage, refundMessage.isEmpty == false { diff --git a/PadelClub/Views/Team/PaymentRequestButton.swift b/PadelClub/Views/Team/PaymentRequestButton.swift new file mode 100644 index 0000000..7ce6843 --- /dev/null +++ b/PadelClub/Views/Team/PaymentRequestButton.swift @@ -0,0 +1,51 @@ +// +// PaymentRequestButton.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/10/2025. +// + + +import SwiftUI +import PadelClubData + +struct PaymentRequestButton: View { + let teamRegistration: TeamRegistration + @State private var isLoading = false + @State private var showAlert = false + @State private var alertMessage = "" + + var body: some View { + Button("Renvoyer email de paiement") { + resendEmail() + } + .disabled(isLoading) + .alert("Résultat", isPresented: $showAlert) { + Button("OK") { } + } message: { + Text(alertMessage) + } + } + + private func resendEmail() { + isLoading = true + Task { + do { + let response = try await PaymentService.resendPaymentEmail( + teamRegistrationId: teamRegistration.id + ) + await MainActor.run { + isLoading = false + alertMessage = response.message + showAlert = true + } + } catch { + await MainActor.run { + isLoading = false + alertMessage = "Erreur lors de l'envoi" + showAlert = true + } + } + } + } +} diff --git a/PadelClub/Views/Team/PaymentService.swift b/PadelClub/Views/Team/PaymentService.swift new file mode 100644 index 0000000..59cdfe5 --- /dev/null +++ b/PadelClub/Views/Team/PaymentService.swift @@ -0,0 +1,24 @@ +class PaymentService { + static func resendPaymentEmail(teamRegistrationId: String) async throws -> SimpleResponse { + let service = try StoreCenter.main.service() + let urlRequest = try service._baseRequest( + servicePath: "resend-payment-email/\(teamRegistrationId)/", + method: .post, + requiresToken: true + ) + + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PaymentError.requestFailed + } + + return try JSON.decoder.decode(SimpleResponse.self, from: data) + } +} + +struct SimpleResponse: Codable { + let success: Bool + let message: String +} diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index a93f24b..9bbab53 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -54,7 +54,10 @@ struct InscriptionManagerView: View { @State private var refreshResult: String? = nil @State private var refreshStatus: Bool? @State private var showLegendView: Bool = false - + @State private var isLoading = false + @State private var showAlert = false + @State private var alertMessage = "" + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -335,6 +338,11 @@ struct InscriptionManagerView: View { } .tint(.master) } + .alert("Requête de paiement", isPresented: $showAlert) { + Button("OK") { } + } message: { + Text(alertMessage) + } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { @@ -373,6 +381,40 @@ struct InscriptionManagerView: View { ToolbarItem(placement: .navigationBarTrailing) { Menu { + + #if PRODTEST + if tournament.enableOnlinePayment { + Button { + isLoading = true + Task { + do { + try await selectedSortedTeams.filter { team in + team.hasPaidOnline() == false && team.hasPaid() == false + }.concurrentForEach { team in + _ = try await PaymentService.resendPaymentEmail(teamRegistrationId: team.id) + } + + await MainActor.run { + isLoading = false + alertMessage = "Relance effectuée avec succès" + showAlert = true + } + } catch { + Logger.error(error) + await MainActor.run { + isLoading = false + alertMessage = "Erreur lors de la requête" + showAlert = true + } + } + } + } label: { + Text("Requête de paiement") + } + .disabled(isLoading) + } + #endif + if tournament.inscriptionClosed() == false { Menu { _sortingTypePickerView() @@ -396,7 +438,7 @@ struct InscriptionManagerView: View { if tournament.isAnimation() == false { Divider() - Section { + Menu { Button("+1 en tableau") { tournament.addWildCard(1, .bracket) _setHash() @@ -408,7 +450,7 @@ struct InscriptionManagerView: View { _setHash() } } - } header: { + } label: { Text("Ajout de wildcards") } @@ -421,21 +463,29 @@ struct InscriptionManagerView: View { } - Divider() - - Button { - presentImportView = true + Menu { + Button { + presentImportView = true + } label: { + Label("Importer un fichier", systemImage: "square.and.arrow.down") + } + Link(destination: URLs.beachPadel.url) { + Label("beach-padel.app.fft.fr", systemImage: "safari") + } } label: { - Label("Importer beach-padel", systemImage: "square.and.arrow.down") - } - Link(destination: URLs.beachPadel.url) { - Label("beach-padel.app.fft.fr", systemImage: "safari") + Text("Beach Padel") } if tournament.inscriptionClosed() == false { Divider() - _importTeamsMenuView(title: "Importer des paires") - _sharingTeamsMenuView() + + Menu { + _importTeamsMenuView(title: "Importer des paires d'un autre tournoi") + _sharingTeamsMenuView() + } label: { + Text("Importer / Exporter") + } + } else { _sharingTeamsMenuView() @@ -459,19 +509,20 @@ struct InscriptionManagerView: View { } //rankingDateSourcePickerView(showDateInLabel: true) - - Divider() - - _sharingTeamsMenuView() - Divider() - - _importTeamsMenuView(title: "Importer des paires") - Button { - presentImportView = true + Menu { + _sharingTeamsMenuView() + + _importTeamsMenuView(title: "Importer des paires d'un autre tournoi") + + Button { + presentImportView = true + } label: { + Label("Importer un fichier", systemImage: "square.and.arrow.down") + } } label: { - Label("Importer un fichier", systemImage: "square.and.arrow.down") + Text("Importer / Exporter") } } } label: { @@ -741,7 +792,7 @@ struct InscriptionManagerView: View { .pickerStyle(.menu) if currentRankSourceDate == SourceFileManager.shared.mostRecentDateAvailable { - Button("Rafraîchir") { + Button("Rafraîchir le rang des joueurs") { confirmUpdateRank = true } }