diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 97d2fa6..d9f0fbb 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -154,6 +154,12 @@ FF2099042EA0D99A003CE880 /* PaymentLinkManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099032EA0D99A003CE880 /* PaymentLinkManagerView.swift */; }; FF2099052EA0D99A003CE880 /* PaymentLinkManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099032EA0D99A003CE880 /* PaymentLinkManagerView.swift */; }; FF2099062EA0D99A003CE880 /* PaymentLinkManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099032EA0D99A003CE880 /* PaymentLinkManagerView.swift */; }; + FF2099082EA140A2003CE880 /* TeamMatchesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099072EA140A2003CE880 /* TeamMatchesView.swift */; }; + FF2099092EA140A2003CE880 /* TeamMatchesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099072EA140A2003CE880 /* TeamMatchesView.swift */; }; + FF20990A2EA140A2003CE880 /* TeamMatchesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2099072EA140A2003CE880 /* TeamMatchesView.swift */; }; + FF20990C2EA1430E003CE880 /* PlayerSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF20990B2EA1430E003CE880 /* PlayerSearchView.swift */; }; + FF20990D2EA1430E003CE880 /* PlayerSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF20990B2EA1430E003CE880 /* PlayerSearchView.swift */; }; + FF20990E2EA1430E003CE880 /* PlayerSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF20990B2EA1430E003CE880 /* PlayerSearchView.swift */; }; FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; @@ -1042,6 +1048,8 @@ FF1F4B7F2BFA0105000B4573 /* tournament-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "tournament-template.html"; sourceTree = ""; }; FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = ""; }; FF2099032EA0D99A003CE880 /* PaymentLinkManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentLinkManagerView.swift; sourceTree = ""; }; + FF2099072EA140A2003CE880 /* TeamMatchesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamMatchesView.swift; sourceTree = ""; }; + FF20990B2EA1430E003CE880 /* PlayerSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSearchView.swift; sourceTree = ""; }; FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanningByCourtView.swift; sourceTree = ""; }; FF2B51602C7E302C00FFF126 /* local.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = local.sqlite; sourceTree = ""; }; FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_1_1.xcdatamodel; sourceTree = ""; }; @@ -1665,6 +1673,7 @@ FF7091692B90F95E00AB08DA /* DateBoxView.swift */, FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */, FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */, + FF20990B2EA1430E003CE880 /* PlayerSearchView.swift */, ); path = Shared; sourceTree = ""; @@ -1895,6 +1904,7 @@ FF7DCD382CC330260041110C /* TeamRestingView.swift */, FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */, FF2099032EA0D99A003CE880 /* PaymentLinkManagerView.swift */, + FF2099072EA140A2003CE880 /* TeamMatchesView.swift */, FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; @@ -2438,11 +2448,13 @@ FF6761582CC7803600CC9BF2 /* DrawLogsView.swift in Sources */, FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */, FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */, + FF20990D2EA1430E003CE880 /* PlayerSearchView.swift in Sources */, FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */, FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */, FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FFE8B5B72DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */, FFD8837A2E1E63880004D7DD /* FederalDataService.swift in Sources */, + FF2099092EA140A2003CE880 /* TeamMatchesView.swift in Sources */, FFBFC3962CF05CBB000EBD8D /* DateMenuView.swift in Sources */, FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, @@ -2716,11 +2728,13 @@ FF4CBFD82C996C0600151637 /* ImportedPlayer+Extensions.swift in Sources */, FF4CBFD92C996C0600151637 /* ClubSearchView.swift in Sources */, FFBFC3972CF05CBB000EBD8D /* DateMenuView.swift in Sources */, + FF20990C2EA1430E003CE880 /* PlayerSearchView.swift in Sources */, FF4CBFDA2C996C0600151637 /* PlayerPopoverView.swift in Sources */, FF4CBFDB2C996C0600151637 /* InscriptionManagerView.swift in Sources */, FFB378362D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */, FF77CE522CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */, FFD883792E1E63880004D7DD /* FederalDataService.swift in Sources */, + FF20990A2EA140A2003CE880 /* TeamMatchesView.swift in Sources */, FF4CBFDC2C996C0600151637 /* ActivityView.swift in Sources */, FF4CBFDE2C996C0600151637 /* CalendarView.swift in Sources */, FF4CBFDF2C996C0600151637 /* FederalTournamentSearchScope.swift in Sources */, @@ -2972,11 +2986,13 @@ FF70FB572C90584900129CC2 /* ImportedPlayer+Extensions.swift in Sources */, FF70FB582C90584900129CC2 /* ClubSearchView.swift in Sources */, FFBFC3952CF05CBB000EBD8D /* DateMenuView.swift in Sources */, + FF20990E2EA1430E003CE880 /* PlayerSearchView.swift in Sources */, FF70FB592C90584900129CC2 /* PlayerPopoverView.swift in Sources */, FF70FB5A2C90584900129CC2 /* InscriptionManagerView.swift in Sources */, FFB378342D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */, FF77CE532CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */, FFD8837B2E1E63880004D7DD /* FederalDataService.swift in Sources */, + FF2099082EA140A2003CE880 /* TeamMatchesView.swift in Sources */, FF70FB5B2C90584900129CC2 /* ActivityView.swift in Sources */, FF70FB5D2C90584900129CC2 /* CalendarView.swift in Sources */, FF70FB5E2C90584900129CC2 /* FederalTournamentSearchScope.swift in Sources */, diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index d0ec6a9..1d876f2 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -43,7 +43,8 @@ struct MenuWarningView: View { } } } label: { - Text("Prévenir") + Label("Prévenir", systemImage: "phone") + .labelStyle(.iconOnly) } .menuStyle(.button) .buttonStyle(.borderedProminent) diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 71e2417..95706fc 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -90,6 +90,8 @@ struct EventSettingsView: View { } } + _linkLabel() + Section { DatePicker(selection: $eventStartDate) { Text(eventStartDate.formatted(.dateTime.weekday(.wide)).capitalized).lineLimit(1) @@ -182,10 +184,6 @@ struct EventSettingsView: View { }) .toolbarBackground(.visible, for: .navigationBar) .toolbar { - ToolbarItem(placement: .topBarTrailing) { - _linkLabel() - } - if focusedField != nil { ToolbarItemGroup(placement: .keyboard) { if focusedField == ._name, eventName.isEmpty == false { @@ -275,7 +273,7 @@ struct EventSettingsView: View { } } } label: { - Text("Liens") + Text("Liens à partager") } } diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index 23caf52..e0251a4 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -76,6 +76,7 @@ enum EventDestination: Identifiable, Selectable, Equatable { struct EventView: View { let event: Event @State private var selectedDestination: EventDestination? + @State private var presentSearchView: Bool = false init(event: Event) { self.event = event @@ -110,6 +111,16 @@ struct EventView: View { } } } + .toolbar(content: { + ToolbarItem(placement: .topBarTrailing) { + Button("Recherche", systemImage: "magnifyingglass") { + presentSearchView = true + } + } + }) + .sheet(isPresented: $presentSearchView, content: { + PlayerSearchView(event: event) + }) .headerProminence(.increased) .toolbarBackground(.visible, for: .navigationBar) .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index d3d7be8..a819287 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -106,9 +106,11 @@ struct EditingTeamView: View { List { #if PRODTEST -// Section { -// if let pid = team.players().first(where: { $0.paymentId != nil })?.paymentId { -// Text(pid) + Section { + if let pid = team.players().first(where: { $0.paymentId != nil })?.paymentId { + Text(pid) + } + } // } else { // if let paste = UIPasteboard.general.string { // RowButtonView("Coller le payment id de l'équipe", role: .destructive) { @@ -128,6 +130,14 @@ struct EditingTeamView: View { // } #endif + Section { + NavigationLink { + TeamMatchesView(team: team) + } label: { + Text("Voir les matchs de l'équipe") + } + } + Section { RowButtonView("Modifier la composition de l'équipe", role: (team.hasRegisteredOnline() || team.hasPaidOnline()) ? .destructive : .none, confirmationMessage: "Vous êtes sur le point de modifier une équipe qui s'est inscrite en ligne.") { editedTeam = team @@ -351,6 +361,11 @@ struct EditingTeamView: View { } } } + .ifAvailableiOS26 { + if #available(iOS 26.0, *) { + $0.navigationSubtitle(tournament.tournamentTitle()) + } + } .alert("Attention", isPresented: hasChanged, actions: { Button("Confirmer") { if walkOut == false && team.walkOut == true { diff --git a/PadelClub/Views/Team/TeamMatchesView.swift b/PadelClub/Views/Team/TeamMatchesView.swift new file mode 100644 index 0000000..fcb4e8d --- /dev/null +++ b/PadelClub/Views/Team/TeamMatchesView.swift @@ -0,0 +1,40 @@ +// +// TeamMatchesView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 16/10/2025. +// + +import SwiftUI +import LeStorage +import PadelClubData + +struct TeamMatchesView: View { + let team: TeamRegistration + + var body: some View { + List { + if let currentMatch = team.currentMatch() { + Section { + MatchRowView(match: currentMatch) + } header: { + Text("Prochain match") + } + } + + let followingMatches = team.followingMatches() + if followingMatches.isEmpty == false { + Section { + ForEach(followingMatches) { match in + MatchRowView(match: match) + } + } header: { + Text("Tous les matchs") + } + } else { + ContentUnavailableView("Aucun match à venir", systemImage: "calendar.badge.exclamation", description: Text("Il n’y a pas de matchs prévus pour cette équipe.")) + } + } + .navigationTitle("Liste des matchs") + } +} diff --git a/PadelClub/Views/Tournament/Shared/PlayerSearchView.swift b/PadelClub/Views/Tournament/Shared/PlayerSearchView.swift new file mode 100644 index 0000000..c266f2a --- /dev/null +++ b/PadelClub/Views/Tournament/Shared/PlayerSearchView.swift @@ -0,0 +1,83 @@ +// +// PlayerSearchView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 16/10/2025. +// + +import SwiftUI +import LeStorage +import PadelClubData + +struct PlayerSearchView: View { + @State private var searchText: String = "" + @State private var presentSearch: Bool = true + @EnvironmentObject var dataStore: DataStore + @Environment(\.dismiss) private var dismiss + + let event: Event + + let tournaments: [Tournament] + let players: [PlayerRegistration] + + init(event: Event) { + self.event = event + self.tournaments = event.tournaments + self.players = event.tournaments.flatMap({ $0.players() }) + } + + var searchedPlayers: [PlayerRegistration] { + if searchText.isEmpty { + return [] + } else { + return players.filter({ $0.contains(searchText) }) + } + } + + var body: some View { + NavigationStack { + let _searchedPlayers = searchedPlayers + let teams = Set(searchedPlayers.compactMap({ $0.team() })) + List { + ForEach(teams.sorted(by: \.tournament)) { team in + if let tournament = team.tournamentObject() { + Section { + NavigationLink { + EditingTeamView(team: team) + .environment(tournament) + } label: { + TeamRowView(team: team) + } + } footer: { + Text(tournament.tournamentTitle(.title)) + } + } + } + } + .ifAvailableiOS26 { + if #available(iOS 26.0, *) { + $0.navigationSubtitle(event.eventTitle()) + } + } + .navigationTitle("Rechercher un joueur") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Fermer", systemImage: "xmark") { + dismiss() + } + } + } + .overlay(content: { + if _searchedPlayers.isEmpty { + if searchText.isEmpty { + ContentUnavailableView("Rechercher des joueurs", systemImage: "magnifyingglass", description: Text("Tapez un nom de joueur ci-dessus pour commencer la recherche.\nVous pouvez aussi rechercher par email, numéro de téléphone, licence ou identifiant de paiement Stripe.")) + } else { + ContentUnavailableView.search(text: searchText) + } + } + }) + .searchable(text: $searchText, isPresented: $presentSearch, placement: .navigationBarDrawer(displayMode: .always), prompt: "Rechercher un joueur") + } + } +} + diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index cbd21d8..ae89ef3 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -18,7 +18,7 @@ struct TournamentView: View { @State var tournament: Tournament @State private var showMoreInfos: Bool = false @State var shouldTournamentBeOver: Bool = false - + @State private var presentSearchView: Bool = false var presentationContext: PresentationContext = .agenda let tournamentSelectionTip: TournamentSelectionTip = TournamentSelectionTip() @@ -186,6 +186,13 @@ struct TournamentView: View { .navigationBarTitleDisplayMode(.inline) .toolbarTitleMenu { if let event = tournament.eventObject() { + Button("Rechercher un joueur", systemImage: "magnifyingglass") { + presentSearchView = true + } + .labelStyle(.titleOnly) + + Divider() + if presentationContext == .agenda { Picker(selection: selectedTournamentId) { ForEach(event.tournaments) { tournament in @@ -334,6 +341,11 @@ struct TournamentView: View { } } } + .sheet(isPresented: $presentSearchView, content: { + if let event = tournament.eventObject() { + PlayerSearchView(event: event) + } + }) .onAppear { TournamentRunningTip.isRunning = tournament.state() == .running Logger.log("Tournament Id = \(self.tournament.id), Payment = \(String(describing: self.tournament.payment))")