diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 4816398..6fd837e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -126,6 +126,12 @@ FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; + FF3A73F32D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */; }; + FF3A73F42D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */; }; + FF3A73F52D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */; }; + FF3A74322D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */; }; + FF3A74332D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */; }; + FF3A74342D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */; }; FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */; }; @@ -1072,6 +1078,8 @@ FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.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 = ""; }; + FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationInfoSheetView.swift; sourceTree = ""; }; + FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionLegendView.swift; sourceTree = ""; }; FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = ""; }; FF3F74F52B919E45004CFE0E /* UmpireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireView.swift; sourceTree = ""; }; FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestination.swift; sourceTree = ""; }; @@ -1340,6 +1348,8 @@ FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, FF2B515F2C7E300500FFF126 /* SeedData */, C4A47D722B72881500ADC637 /* Views */, + FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */, + FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */, FF3F74FD2B91A087004CFE0E /* ViewModel */, C4A47D5F2B6D3B2D00ADC637 /* Data */, FFF8ACD02B9238A2008466FA /* Utils */, @@ -2411,6 +2421,7 @@ FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */, + FF3A74332D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */, C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */, C4FC2E2B2C2C0E4D0021F3BF /* TournamentStore.swift in Sources */, @@ -2499,6 +2510,7 @@ FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */, FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */, + FF3A73F32D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */, FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */, FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, @@ -2699,6 +2711,7 @@ FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */, FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */, FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */, + FF3A74322D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, FF4CBF9E2C996C0600151637 /* NetworkManagerError.swift in Sources */, FF4CBF9F2C996C0600151637 /* Tournament.swift in Sources */, FF4CBFA02C996C0600151637 /* TournamentStore.swift in Sources */, @@ -2787,6 +2800,7 @@ FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */, FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */, FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */, + FF3A73F52D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, FF4CBFF02C996C0600151637 /* URLs.swift in Sources */, FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */, FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */, @@ -2966,6 +2980,7 @@ FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */, FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */, FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */, + FF3A74342D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */, FF70FB1D2C90584900129CC2 /* NetworkManagerError.swift in Sources */, FF70FB1E2C90584900129CC2 /* Tournament.swift in Sources */, FF70FB1F2C90584900129CC2 /* TournamentStore.swift in Sources */, @@ -3054,6 +3069,7 @@ FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */, FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */, FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */, + FF3A73F42D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */, FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */, FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */, FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */, diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index f9205e5..9638cc2 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -40,22 +40,21 @@ final class PlayerRegistration: ModelObject, Storable { var hasArrived: Bool = false var coach: Bool = false var captain: Bool = false + var registeredOnline: Bool = false func localizedSourceLabel() -> String { switch source { - case .frenchFederation: + case .frenchFederation, .onlineRegistration: return "Via la base fédérale" case .beachPadel: return "Via le fichier beach-padel" - case .onlineRegistration: - return "Via un inscription en ligne" case nil: return "Manuellement" } } - init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false) { + init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false, registeredOnline: Bool = false) { self.teamRegistration = teamRegistration self.firstName = firstName self.lastName = lastName @@ -76,6 +75,7 @@ final class PlayerRegistration: ModelObject, Storable { self.hasArrived = hasArrived self.captain = captain self.coach = coach + self.registeredOnline = registeredOnline } internal init(importedPlayer: ImportedPlayer) { @@ -403,6 +403,7 @@ defer { case _hasArrived = "hasArrived" case _coach = "coach" case _captain = "captain" + case _registeredOnline = "registeredOnline" } @@ -433,6 +434,8 @@ defer { email = try container.decodeIfPresent(String.self, forKey: ._email) birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) source = try container.decodeIfPresent(PlayerDataSource.self, forKey: ._source) + registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false + } func encode(to encoder: Encoder) throws { @@ -460,6 +463,7 @@ defer { try container.encode(hasArrived, forKey: ._hasArrived) try container.encode(captain, forKey: ._captain) try container.encode(coach, forKey: ._coach) + try container.encode(registeredOnline, forKey: ._registeredOnline) } enum PlayerDataSource: Int, Codable { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 9fcefab..a24e355 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -40,7 +40,7 @@ final class TeamRegistration: ModelObject, Storable { var pointsEarned: Int? func hasRegisteredOnline() -> Bool { - players().anySatisfy({ $0.source == .onlineRegistration }) + players().anySatisfy({ $0.registeredOnline }) } func unrankedOrUnknown() -> Bool { @@ -248,9 +248,9 @@ final class TeamRegistration: ModelObject, Storable { self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) } - func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String { + func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&") -> String { if let name { return name } - return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ") + return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " \(separator) ") } func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { @@ -384,19 +384,16 @@ final class TeamRegistration: ModelObject, Storable { } func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { + guard let registrationDate else { return nil } + + let formattedDate = registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) + let onlineSuffix = hasRegisteredOnline() ? " en ligne" : "" + switch exportFormat { case .rawText: - if let registrationDate { - return "Inscrit le " + registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } + return "Inscrit\(onlineSuffix) le \(formattedDate)" case .csv: - if let registrationDate { - return registrationDate.formatted(.dateTime.weekday().day().month().hour().minute()) - } else { - return nil - } + return formattedDate } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 1df0adf..a347ea0 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -141,7 +141,11 @@ final class Tournament : ModelObject, Storable { #if DEBUG self.isPrivate = false #else - self.isPrivate = Guard.main.purchasedTransactions.isEmpty + if Guard.main.currentPlan == .monthlyUnlimited { + self.isPrivate = true + } else { + self.isPrivate = Guard.main.purchasedTransactions.isEmpty + } #endif self.groupStageFormat = groupStageFormat self.roundFormat = roundFormat diff --git a/PadelClub/InscriptionLegendView.swift b/PadelClub/InscriptionLegendView.swift new file mode 100644 index 0000000..ed1db83 --- /dev/null +++ b/PadelClub/InscriptionLegendView.swift @@ -0,0 +1,74 @@ +// +// InscriptionLegendView.swift +// PadelClub +// +// Created by razmig on 15/01/2025. +// + +import SwiftUI + +struct InscriptionLegendView: View { + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationView { + List { + Section { + ForEach(RoundRule.colors.prefix(6).indices, id: \.self) { colorIndex in + Text("Équipe placée en \(RoundRule.roundName(fromRoundIndex: colorIndex))") + .listRowView(isActive: true, color: Color(uiColor: .init(fromHex: RoundRule.colors[colorIndex])), hideColorVariation: true, alignment: .trailing) + } + } + + Section { + Text("Équipe placée en poule") + .listRowView(isActive: true, color: .blue, hideColorVariation: true, alignment: .trailing) + + } + + Section { + Text("Équipe estimée en tableau") + .listRowView(isActive: true, color: .mint, hideColorVariation: true, alignment: .trailing) + Text("Équipe estimée en poule") + .listRowView(isActive: true, color: .cyan, hideColorVariation: true, alignment: .trailing) + + } + + Section { + Text("Équipe en liste d'attente") + .listRowView(isActive: true, color: .gray, hideColorVariation: true, alignment: .trailing) + Text("Équipe forfaite") + .listRowView(isActive: true, color: .logoRed, hideColorVariation: true, alignment: .trailing) + } + + Section { + Label("Inscrit en ligne", systemImage: "person.badge.shield.checkmark.fill") + } footer: { + Text("Icône indiquant que le joueur s'est inscrit en ligne.") + } + + Section { + Text("Équipe ayant un joueur à vérifier") + .listRowView(isActive: true, color: .logoRed, hideColorVariation: true, backgroundColor: .logoRed, alignment: .leading) + } footer: { + Text("Une fois que vous avez importé votre fichier, Padel Club vous affiche ainsi les équipes ayant des joueurs ne provenant pas du fichier ni de la base fédérale.") + } + + Section { + Text("Équipe ayant un joueur ne provenant pas du fichier beach-padel") + .listRowView(isActive: true, color: .beige, hideColorVariation: true, backgroundColor: .beige, alignment: .leading) + } footer: { + Text("Une fois que vous avez importé votre fichier, Padel Club vous affiche ainsi les équipes ayant des joueurs ne provenant pas du fichier.") + } + + } + .headerProminence(.increased) + .navigationTitle("Légende") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarItems(trailing: Button("Fermer") { + dismiss() + }) + } + } +} diff --git a/PadelClub/RegistrationInfoSheetView.swift b/PadelClub/RegistrationInfoSheetView.swift new file mode 100644 index 0000000..efbf3ac --- /dev/null +++ b/PadelClub/RegistrationInfoSheetView.swift @@ -0,0 +1,89 @@ +// +// RegistrationInfoSheetView.swift +// PadelClub +// +// Created by razmig on 15/01/2025. +// + +import SwiftUI + +struct RegistrationInfoSheetView: View { + @Environment(\.dismiss) private var dismiss + let registrationInfoText: String = + """ + Comment fonctionnent les inscriptions en ligne ? + + Les inscriptions en ligne permettent aux joueurs de s'inscrire directement au tournoi via la plateforme. Voici les informations importantes à connaître : + + Conditions d'inscription : + - Un compte Padel Club est requis pour s'inscrire + - Une licence valide peut être nécessaire + - Les équipes des tournois homologués doivent être composées de 2 joueurs + - Les animations ont moins de restrictions + + Déroulement des inscriptions : + 1. Les inscriptions peuvent avoir une date et heure d'ouverture définies par l'organisateur + 2. Le tournoi peut avoir une capacité maximale d'équipes + 3. Si une capacité maximale est définie, les nouvelles inscriptions seront placées en liste d'attente une fois celle-ci atteinte + 4. La liste d'attente peut également avoir une limite maximale d'équipes + 5. Les inscriptions peuvent se terminer à une date limite fixée par l'organisateur + + Désinscription : + La désinscription est possible tant que : + - Le tournoi n'a pas commencé + - La date limite d'inscription n'est pas dépassée + - Les inscriptions n'ont pas été clôturées par l'organisateur + + Validation des inscriptions : + - L'inscription n'est définitive qu'après validation des critères d'éligibilité (catégorie, classement, âge...) + - En cas de désistement d'une équipe inscrite, la première équipe en liste d'attente est automatiquement intégrée au tableau + - Une équipe en liste d'attente peut se désinscrire à tout moment selon les mêmes conditions + + L'organisateur se réserve le droit de modifier ces conditions ou de clôturer les inscriptions de manière anticipée. + """ + var body: some View { + NavigationView { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + // Title + Text("Inscriptions en ligne") + .font(.title) + .fontWeight(.bold) + .padding(.bottom, 5) + + // Content sections + ForEach(registrationInfoText.components(separatedBy: "\n\n"), id: \.self) { section in + if !section.isEmpty { + VStack(alignment: .leading, spacing: 10) { + if section.contains(":") { + Text(section.components(separatedBy: ":")[0]) + .font(.headline) + .foregroundColor(.primary) + + let bulletPoints = section.components(separatedBy: "\n-") + if bulletPoints.count > 1 { + ForEach(bulletPoints.dropFirst(), id: \.self) { point in + HStack(alignment: .top) { + Text("•") + .padding(.trailing, 5) + Text(point) + .fixedSize(horizontal: false, vertical: true) + } + } + } + } else { + Text(section) + } + } + .padding(.bottom, 10) + } + } + } + .padding() + } + .navigationBarItems(trailing: Button("Fermer") { + dismiss() + }) + } + } +} diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index b346a14..ff9b1d8 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -1657,7 +1657,7 @@ enum PlayersCountRange: Int, CaseIterable { } enum RoundRule { - static let colors = ["#99ff99", "#66ff66", "#33cc33", "#009900", "#006600", "#006600", "#006600", "#006600", "#006600", "#006600"] + static let colors = ["#99ff99", "#66ff66", "#33cc33", "#009900", "#006600", "#336633", "#DD6600", "#EE6633", "#EE6633", "#EE6633"] static func loserBrackets(index: Int) -> [String] { switch index { diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index c3e145c..c44c9b3 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -615,10 +615,14 @@ struct OnlineRegistrationTip: Tip { } var actions: [Action] { - Action(id: ActionKey.enableOnlineRegistration.rawValue, title: "Activer dans les réglages du tournoi") + [ + Action(id: ActionKey.more.rawValue, title: "En savoir plus"), + Action(id: ActionKey.enableOnlineRegistration.rawValue, title: "Activer dans les réglages du tournoi") + ] } enum ActionKey: String { + case more = "more" case enableOnlineRegistration = "enableOnlineRegistration" } } diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index ab452e0..9c89875 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -24,6 +24,7 @@ struct EditingTeamView: View { @FocusState private var focusedField: TeamRegistration.CodingKeys? @State private var presentOnlineRegistrationWarning: Bool = false @State private var currentWaitingList: TeamRegistration? + @State private var presentTeamToWarn: Bool = false var messageSentFailed: Binding { Binding { @@ -64,7 +65,7 @@ struct EditingTeamView: View { var body: some View { List { Section { - RowButtonView("Modifier la composition de l'équipe") { + RowButtonView("Modifier la composition de l'équipe", role: team.hasRegisteredOnline() ? .destructive : .none) { editedTeam = team } @@ -72,6 +73,7 @@ struct EditingTeamView: View { } header: { if team.hasRegisteredOnline() { Text("Inscription en ligne") + .foregroundStyle(.master) } else { Text("Inscription par vous-même") } @@ -88,6 +90,7 @@ struct EditingTeamView: View { } } } + .headerProminence(.increased) Section { DatePicker(selection: $registrationDate) { @@ -206,22 +209,34 @@ struct EditingTeamView: View { } } footer: { if team.hasRegisteredOnline() { - Text("Attention, supprimer cette équipe notifiera par email que leur inscription a été annulée.") + Text("Attention, supprimer cette équipe notifiera par email que leur inscription a été annulée.").foregroundStyle(.logoRed) } } } - .alert("Attention", isPresented: $presentOnlineRegistrationWarning, actions: { + .sheet(isPresented: $presentTeamToWarn) { if let currentWaitingList { - Button("Prévenir") { - + NavigationStack { + EditingTeamView(team: currentWaitingList) } + .tint(.master) + } + } + .alert("Attention", isPresented: $presentOnlineRegistrationWarning, actions: { + if currentWaitingList != nil { + Button("Voir l'équipe") { + self.presentTeamToWarn = true + } + Button("OK") { self.currentWaitingList = nil + self.presentOnlineRegistrationWarning = false } } }, message: { - Text("Cette équipe, inscrite en ligne, rentre dans votre sélection suite à la modification que vous venez de faire, voulez-vous les prévenir ?") + if let currentWaitingList { + Text("L'équipe \(currentWaitingList.teamLabel(separator: "/")), inscrite en ligne, rentre dans votre sélection suite à la modification que vous venez de faire, voulez-vous les prévenir ?") + } }) .navigationBarBackButtonHidden(focusedField != nil) .toolbar(content: { @@ -351,6 +366,8 @@ struct EditingTeamView: View { } catch { Logger.error(error) } + + _checkOnlineRegistrationWarning() } private var _networkErrorMessage: String { diff --git a/PadelClub/Views/Team/TeamRowView.swift b/PadelClub/Views/Team/TeamRowView.swift index f32365b..c26766d 100644 --- a/PadelClub/Views/Team/TeamRowView.swift +++ b/PadelClub/Views/Team/TeamRowView.swift @@ -118,7 +118,12 @@ struct TeamRowView: View { var body: some View { ForEach(team.players()) { player in - Text(player.playerLabel()).lineLimit(1).truncationMode(.tail) + HStack { + if player.registeredOnline { + Image(systemName: "person.badge.shield.checkmark.fill") + } + Text(player.playerLabel()).lineLimit(1).truncationMode(.tail) + } } } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 6138632..6942db4 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -59,81 +59,6 @@ struct InscriptionManagerView: View { return self.tournament.tournamentStore } - enum LegendPositionTip: Int, Identifiable, CaseIterable { - var id: Int { self.rawValue } - case groupStage - case bracket - - func legendDescriptionLabel() -> String { - return "" - } - - var isActive: Bool { - switch self { - case .groupStage: - return true - case .bracket: - return true - } - } - - var color: Color { - switch self { - case .groupStage: - return .red - case .bracket: - return .green - } - } - - var hideColorVariation: Bool { - switch self { - case .groupStage: - return true - case .bracket: - return true - } - } - } - - enum LegendInscriptionTip: Int, Identifiable, CaseIterable { - var id: Int { self.rawValue } - case groupStage - case bracket - - func legendDescriptionLabel() -> String { - return "" - } - - var isActive: Bool { - switch self { - case .groupStage: - return true - case .bracket: - return true - } - } - - var color: Color { - switch self { - case .groupStage: - return .red - case .bracket: - return .green - } - } - - var hideColorVariation: Bool { - switch self { - case .groupStage: - return true - case .bracket: - return true - } - } - - } - enum SortingMode: Int, Identifiable, CaseIterable { var id: Int { self.rawValue } case registrationDate @@ -455,11 +380,13 @@ struct InscriptionManagerView: View { Section { Button("+1 en tableau") { tournament.addWildCard(1, .bracket) + _setHash() } if tournament.groupStageCount > 0 { Button("+1 en poules") { tournament.addWildCard(1, .groupStage) + _setHash() } } } header: { @@ -468,6 +395,7 @@ struct InscriptionManagerView: View { Button("Bloquer une place") { tournament.addEmptyTeamRegistration(1) + _setHash() } Divider() @@ -675,24 +603,20 @@ struct InscriptionManagerView: View { Section { ForEach(teams) { team in let teamIndex = team.index(in: sortedTeams) + let color: Color? = isImported ? (team.unrankedOrUnknown() ? .logoRed : (team.isImported() == false ? .beige : nil)) : nil + NavigationLink { EditingTeamView(team: team) .environment(tournament) } label: { TeamRowView(team: team) - if isImported && team.isImported() == false { - Text("ne provient pas du fichier beach-padel").foregroundStyle(.red) - } } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if tournament.enableOnlineRegistration == false { _teamDeleteButtonView(team) } } - .listRowView(isActive: team.hasRegisteredOnline(), color: .master, hideColorVariation: true) - .listRowView(isActive: team.unrankedOrUnknown(), color: .logoYellow, hideColorVariation: true) - .listRowView(isActive: isImported && team.isImported() == false, color: .red, hideColorVariation: false) - .listRowView(isActive: true, color: team.initialRoundColor() ?? tournament.cutLabelColor(index: teamIndex, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count), hideColorVariation: true, alignment: .trailing) + .listRowView(isActive: true, color: team.initialRoundColor() ?? tournament.cutLabelColor(index: teamIndex, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count), hideColorVariation: true, backgroundColor: color, alignment: .trailing) } } header: { if filterMode == .all && walkoutTeams.isEmpty == false { @@ -701,31 +625,11 @@ struct InscriptionManagerView: View { Text("\(teams.count.formatted()) équipe\(teams.count.pluralSuffix)") } } footer: { - FooterButtonView("Légende") { + FooterButtonView("Légende des codes couleurs") { showLegendView = true } } .headerProminence(.increased) - .sheet(isPresented: $showLegendView) { - List { - Section { - ForEach(LegendInscriptionTip.allCases) { legend in - Text(legend.legendDescriptionLabel()) - .listRowView(isActive: legend.isActive, color: legend.color, hideColorVariation: legend.hideColorVariation) - } - } header: { - Text("Statut de l'inscription") - } - Section { - ForEach(LegendPositionTip.allCases) { legend in - Text(legend.legendDescriptionLabel()) - .listRowView(isActive: legend.isActive, color: legend.color, hideColorVariation: legend.hideColorVariation, alignment: .trailing) - } - } header: { - Text("Statut de la position dans le tournoi") - } - } - } } else { ForEach(teams) { team in let teamIndex = team.index(in: sortedTeams) @@ -754,6 +658,9 @@ struct InscriptionManagerView: View { .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites")) .keyboardType(.alphabet) .autocorrectionDisabled() + .sheet(isPresented: $showLegendView) { + InscriptionLegendView() + } } @ViewBuilder diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index efddaa1..73bbbe7 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -24,6 +24,7 @@ struct RegistrationSetupView: View { @State private var licenseIsRequired: Bool @State private var minPlayerPerTeam: Int @State private var maxPlayerPerTeam: Int + @State private var showMoreInfos: Bool = false @State private var hasChanges: Bool = false @@ -78,7 +79,14 @@ struct RegistrationSetupView: View { Text("Activer") } } footer: { - Text("Les inscriptions en ligne permettent à des joueurs de s'inscrire à votre tournoi en passant par le site Padel Club. Vous verrez alors votre liste d'inscription s'agrandir dans la vue Gestion des Inscriptions de l'application.") + VStack(alignment: .leading) { + Text("Les inscriptions en ligne permettent à des joueurs de s'inscrire à votre tournoi en passant par le site Padel Club. Vous verrez alors votre liste d'inscription s'agrandir dans la vue Gestion des Inscriptions de l'application.") + + FooterButtonView("En savoir plus") { + self.showMoreInfos = true + + } + } } if enableOnlineRegistration { @@ -191,6 +199,9 @@ struct RegistrationSetupView: View { ) } } + .sheet(isPresented: $showMoreInfos) { + RegistrationInfoSheetView() + } .toolbar(content: { if hasChanges { ToolbarItem(placement: .topBarLeading) { diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 8de233d..1e2a8ea 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -14,6 +14,8 @@ struct TournamentView: View { @Environment(NavigationViewModel.self) var navigation: NavigationViewModel @State var tournament: Tournament + @State private var showMoreInfos: Bool = false + var presentationContext: PresentationContext = .agenda let tournamentSelectionTip: TournamentSelectionTip = TournamentSelectionTip() @@ -62,8 +64,17 @@ struct TournamentView: View { } case .initial: if tournament.enableOnlineRegistration == false { - TipView(onlineRegistrationTip) { actions in - navigation.path.append(Screen.settings) + TipView(onlineRegistrationTip) { action in + if let actionKey = OnlineRegistrationTip.ActionKey(rawValue: action.id) { + switch actionKey { + case .enableOnlineRegistration: + navigation.path.append(Screen.settings) + case .more: + self.showMoreInfos = true + } + } else { + print("Unknown action: \(action.id)") + } } .tipStyle(tint: .master, asSection: true) } @@ -74,10 +85,19 @@ struct TournamentView: View { TournamentInitView(tournament: tournament) case .build: if tournament.enableOnlineRegistration == false { - TipView(onlineRegistrationTip) { actions in - navigation.path.append(Screen.settings) + TipView(onlineRegistrationTip) { action in + if let actionKey = OnlineRegistrationTip.ActionKey(rawValue: action.id) { + switch actionKey { + case .enableOnlineRegistration: + navigation.path.append(Screen.settings) + case .more: + self.showMoreInfos = true + } + } else { + print("Unknown action: \(action.id)") + } } - .tipStyle(tint: .green, asSection: true) + .tipStyle(tint: .master, asSection: true) } Section { @@ -101,6 +121,9 @@ struct TournamentView: View { TournamentRunningView(tournament: tournament) } } + .sheet(isPresented: $showMoreInfos) { + RegistrationInfoSheetView() + } } .environment(tournament) .id(tournament.id) diff --git a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift index 6cfc20a..02dcbc2 100644 --- a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift +++ b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift @@ -11,10 +11,11 @@ struct ListRowViewModifier: ViewModifier { let isActive: Bool let color: Color var hideColorVariation: Bool = false + var backgroundColor: Color? = nil let alignment: Alignment func colorVariation() -> Color { - hideColorVariation ? Color(uiColor: .systemBackground) : color.variation() + hideColorVariation ? (backgroundColor ?? Color(uiColor: .systemBackground)) : color.variation() } func body(content: Content) -> some View { @@ -33,7 +34,7 @@ struct ListRowViewModifier: ViewModifier { } extension View { - func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false, alignment: Alignment = .leading) -> some View { - modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation, alignment: alignment)) + func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false, backgroundColor: Color? = nil, alignment: Alignment = .leading) -> some View { + modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation, backgroundColor: backgroundColor, alignment: alignment)) } } diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index a929f03..2643987 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -242,7 +242,7 @@ final class ServerDataTests: XCTestCase { return } - let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerRegistration.PlayerPaymentType.cash, sex: PlayerRegistration.PlayerSexType.male, tournamentPlayed: 2, points: 33, clubName: "le club", ligueName: "la league", assimilation: "ass", phoneNumber: "123123", email: "email@email.com", birthdate: nil, computedRank: 222, source: PlayerRegistration.PlayerDataSource.frenchFederation, hasArrived: true, coach: true, captain: true) + let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerRegistration.PlayerPaymentType.cash, sex: PlayerRegistration.PlayerSexType.male, tournamentPlayed: 2, points: 33, clubName: "le club", ligueName: "la league", assimilation: "ass", phoneNumber: "123123", email: "email@email.com", birthdate: nil, computedRank: 222, source: PlayerRegistration.PlayerDataSource.frenchFederation, hasArrived: true, coach: true, captain: true, registeredOnline: true) let pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration) assert(pr.teamRegistration == playerRegistration.teamRegistration) @@ -264,6 +264,7 @@ final class ServerDataTests: XCTestCase { assert(pr.hasArrived == playerRegistration.hasArrived) assert(pr.captain == playerRegistration.captain) assert(pr.coach == playerRegistration.coach) + assert(pr.registeredOnline == playerRegistration.registeredOnline) }