online_reg
Raz 10 months ago
parent 945f9ddf29
commit 457900f64a
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 12
      PadelClub/Data/PlayerRegistration.swift
  3. 23
      PadelClub/Data/TeamRegistration.swift
  4. 6
      PadelClub/Data/Tournament.swift
  5. 74
      PadelClub/InscriptionLegendView.swift
  6. 89
      PadelClub/RegistrationInfoSheetView.swift
  7. 2
      PadelClub/Utils/PadelRule.swift
  8. 6
      PadelClub/Utils/Tips.swift
  9. 29
      PadelClub/Views/Team/EditingTeamView.swift
  10. 7
      PadelClub/Views/Team/TeamRowView.swift
  11. 113
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  12. 13
      PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift
  13. 33
      PadelClub/Views/Tournament/TournamentView.swift
  14. 7
      PadelClub/Views/ViewModifiers/ListRowViewModifier.swift
  15. 3
      PadelClubTests/ServerDataTests.swift

@ -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 = "<group>"; };
FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationInfoSheetView.swift; sourceTree = "<group>"; };
FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionLegendView.swift; sourceTree = "<group>"; };
FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = "<group>"; };
FF3F74F52B919E45004CFE0E /* UmpireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireView.swift; sourceTree = "<group>"; };
FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestination.swift; sourceTree = "<group>"; };
@ -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 */,

@ -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 {

@ -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
}
}

@ -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

@ -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()
})
}
}
}

@ -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 é 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()
})
}
}
}

@ -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 {

@ -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"
}
}

@ -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<Bool> {
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 {

@ -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)
}
}
}
}

@ -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

@ -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) {

@ -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)

@ -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))
}
}

@ -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)
}

Loading…
Cancel
Save