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. 4
      PadelClub/Data/Tournament.swift
  5. 74
      PadelClub/InscriptionLegendView.swift
  6. 89
      PadelClub/RegistrationInfoSheetView.swift
  7. 2
      PadelClub/Utils/PadelRule.swift
  8. 4
      PadelClub/Utils/Tips.swift
  9. 27
      PadelClub/Views/Team/EditingTeamView.swift
  10. 5
      PadelClub/Views/Team/TeamRowView.swift
  11. 113
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  12. 11
      PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift
  13. 29
      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 */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; };
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; };
FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; 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 */; }; FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; };
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; };
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestination.swift; sourceTree = "<group>"; };
@ -1340,6 +1348,8 @@
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */,
FF2B515F2C7E300500FFF126 /* SeedData */, FF2B515F2C7E300500FFF126 /* SeedData */,
C4A47D722B72881500ADC637 /* Views */, C4A47D722B72881500ADC637 /* Views */,
FF3A73F22D37C34C007E3032 /* RegistrationInfoSheetView.swift */,
FF3A74312D37DCF2007E3032 /* InscriptionLegendView.swift */,
FF3F74FD2B91A087004CFE0E /* ViewModel */, FF3F74FD2B91A087004CFE0E /* ViewModel */,
C4A47D5F2B6D3B2D00ADC637 /* Data */, C4A47D5F2B6D3B2D00ADC637 /* Data */,
FFF8ACD02B9238A2008466FA /* Utils */, FFF8ACD02B9238A2008466FA /* Utils */,
@ -2411,6 +2421,7 @@
FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */,
FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */,
FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */, FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */,
FF3A74332D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */,
FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */, FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */,
C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */, C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */,
C4FC2E2B2C2C0E4D0021F3BF /* TournamentStore.swift in Sources */, C4FC2E2B2C2C0E4D0021F3BF /* TournamentStore.swift in Sources */,
@ -2499,6 +2510,7 @@
FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */, FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */, FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */,
FF3A73F32D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */,
C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */, C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */,
FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */, FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */,
FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */,
@ -2699,6 +2711,7 @@
FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */, FF4CBF9B2C996C0600151637 /* PlayerRegistration.swift in Sources */,
FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */, FF4CBF9C2C996C0600151637 /* ImportedPlayerView.swift in Sources */,
FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */, FF4CBF9D2C996C0600151637 /* EditingTeamView.swift in Sources */,
FF3A74322D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */,
FF4CBF9E2C996C0600151637 /* NetworkManagerError.swift in Sources */, FF4CBF9E2C996C0600151637 /* NetworkManagerError.swift in Sources */,
FF4CBF9F2C996C0600151637 /* Tournament.swift in Sources */, FF4CBF9F2C996C0600151637 /* Tournament.swift in Sources */,
FF4CBFA02C996C0600151637 /* TournamentStore.swift in Sources */, FF4CBFA02C996C0600151637 /* TournamentStore.swift in Sources */,
@ -2787,6 +2800,7 @@
FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */, FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */,
FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */, FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */,
FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */, FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */,
FF3A73F52D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */,
FF4CBFF02C996C0600151637 /* URLs.swift in Sources */, FF4CBFF02C996C0600151637 /* URLs.swift in Sources */,
FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */, FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */,
FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */, FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */,
@ -2966,6 +2980,7 @@
FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */, FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */,
FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */, FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */,
FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */, FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */,
FF3A74342D37DCF2007E3032 /* InscriptionLegendView.swift in Sources */,
FF70FB1D2C90584900129CC2 /* NetworkManagerError.swift in Sources */, FF70FB1D2C90584900129CC2 /* NetworkManagerError.swift in Sources */,
FF70FB1E2C90584900129CC2 /* Tournament.swift in Sources */, FF70FB1E2C90584900129CC2 /* Tournament.swift in Sources */,
FF70FB1F2C90584900129CC2 /* TournamentStore.swift in Sources */, FF70FB1F2C90584900129CC2 /* TournamentStore.swift in Sources */,
@ -3054,6 +3069,7 @@
FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */, FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */,
FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */, FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */,
FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */, FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */,
FF3A73F42D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */,
FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */, FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */,
FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */, FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */,
FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */, FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */,

@ -40,22 +40,21 @@ final class PlayerRegistration: ModelObject, Storable {
var hasArrived: Bool = false var hasArrived: Bool = false
var coach: Bool = false var coach: Bool = false
var captain: Bool = false var captain: Bool = false
var registeredOnline: Bool = false
func localizedSourceLabel() -> String { func localizedSourceLabel() -> String {
switch source { switch source {
case .frenchFederation: case .frenchFederation, .onlineRegistration:
return "Via la base fédérale" return "Via la base fédérale"
case .beachPadel: case .beachPadel:
return "Via le fichier beach-padel" return "Via le fichier beach-padel"
case .onlineRegistration:
return "Via un inscription en ligne"
case nil: case nil:
return "Manuellement" 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.teamRegistration = teamRegistration
self.firstName = firstName self.firstName = firstName
self.lastName = lastName self.lastName = lastName
@ -76,6 +75,7 @@ final class PlayerRegistration: ModelObject, Storable {
self.hasArrived = hasArrived self.hasArrived = hasArrived
self.captain = captain self.captain = captain
self.coach = coach self.coach = coach
self.registeredOnline = registeredOnline
} }
internal init(importedPlayer: ImportedPlayer) { internal init(importedPlayer: ImportedPlayer) {
@ -403,6 +403,7 @@ defer {
case _hasArrived = "hasArrived" case _hasArrived = "hasArrived"
case _coach = "coach" case _coach = "coach"
case _captain = "captain" case _captain = "captain"
case _registeredOnline = "registeredOnline"
} }
@ -433,6 +434,8 @@ defer {
email = try container.decodeIfPresent(String.self, forKey: ._email) email = try container.decodeIfPresent(String.self, forKey: ._email)
birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate)
source = try container.decodeIfPresent(PlayerDataSource.self, forKey: ._source) source = try container.decodeIfPresent(PlayerDataSource.self, forKey: ._source)
registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
@ -460,6 +463,7 @@ defer {
try container.encode(hasArrived, forKey: ._hasArrived) try container.encode(hasArrived, forKey: ._hasArrived)
try container.encode(captain, forKey: ._captain) try container.encode(captain, forKey: ._captain)
try container.encode(coach, forKey: ._coach) try container.encode(coach, forKey: ._coach)
try container.encode(registeredOnline, forKey: ._registeredOnline)
} }
enum PlayerDataSource: Int, Codable { enum PlayerDataSource: Int, Codable {

@ -40,7 +40,7 @@ final class TeamRegistration: ModelObject, Storable {
var pointsEarned: Int? var pointsEarned: Int?
func hasRegisteredOnline() -> Bool { func hasRegisteredOnline() -> Bool {
players().anySatisfy({ $0.source == .onlineRegistration }) players().anySatisfy({ $0.registeredOnline })
} }
func unrankedOrUnknown() -> Bool { func unrankedOrUnknown() -> Bool {
@ -248,9 +248,9 @@ final class TeamRegistration: ModelObject, Storable {
self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) 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 } 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 { func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String {
@ -384,19 +384,16 @@ final class TeamRegistration: ModelObject, Storable {
} }
func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { 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 { switch exportFormat {
case .rawText: case .rawText:
if let registrationDate { return "Inscrit\(onlineSuffix) le \(formattedDate)"
return "Inscrit le " + registrationDate.formatted(.dateTime.weekday().day().month().hour().minute())
} else {
return nil
}
case .csv: case .csv:
if let registrationDate { return formattedDate
return registrationDate.formatted(.dateTime.weekday().day().month().hour().minute())
} else {
return nil
}
} }
} }

@ -141,7 +141,11 @@ final class Tournament : ModelObject, Storable {
#if DEBUG #if DEBUG
self.isPrivate = false self.isPrivate = false
#else #else
if Guard.main.currentPlan == .monthlyUnlimited {
self.isPrivate = true
} else {
self.isPrivate = Guard.main.purchasedTransactions.isEmpty self.isPrivate = Guard.main.purchasedTransactions.isEmpty
}
#endif #endif
self.groupStageFormat = groupStageFormat self.groupStageFormat = groupStageFormat
self.roundFormat = roundFormat 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 { 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] { static func loserBrackets(index: Int) -> [String] {
switch index { switch index {

@ -615,10 +615,14 @@ struct OnlineRegistrationTip: Tip {
} }
var actions: [Action] { var actions: [Action] {
[
Action(id: ActionKey.more.rawValue, title: "En savoir plus"),
Action(id: ActionKey.enableOnlineRegistration.rawValue, title: "Activer dans les réglages du tournoi") Action(id: ActionKey.enableOnlineRegistration.rawValue, title: "Activer dans les réglages du tournoi")
]
} }
enum ActionKey: String { enum ActionKey: String {
case more = "more"
case enableOnlineRegistration = "enableOnlineRegistration" case enableOnlineRegistration = "enableOnlineRegistration"
} }
} }

@ -24,6 +24,7 @@ struct EditingTeamView: View {
@FocusState private var focusedField: TeamRegistration.CodingKeys? @FocusState private var focusedField: TeamRegistration.CodingKeys?
@State private var presentOnlineRegistrationWarning: Bool = false @State private var presentOnlineRegistrationWarning: Bool = false
@State private var currentWaitingList: TeamRegistration? @State private var currentWaitingList: TeamRegistration?
@State private var presentTeamToWarn: Bool = false
var messageSentFailed: Binding<Bool> { var messageSentFailed: Binding<Bool> {
Binding { Binding {
@ -64,7 +65,7 @@ struct EditingTeamView: View {
var body: some View { var body: some View {
List { List {
Section { Section {
RowButtonView("Modifier la composition de l'équipe") { RowButtonView("Modifier la composition de l'équipe", role: team.hasRegisteredOnline() ? .destructive : .none) {
editedTeam = team editedTeam = team
} }
@ -72,6 +73,7 @@ struct EditingTeamView: View {
} header: { } header: {
if team.hasRegisteredOnline() { if team.hasRegisteredOnline() {
Text("Inscription en ligne") Text("Inscription en ligne")
.foregroundStyle(.master)
} else { } else {
Text("Inscription par vous-même") Text("Inscription par vous-même")
} }
@ -88,6 +90,7 @@ struct EditingTeamView: View {
} }
} }
} }
.headerProminence(.increased)
Section { Section {
DatePicker(selection: $registrationDate) { DatePicker(selection: $registrationDate) {
@ -206,22 +209,34 @@ struct EditingTeamView: View {
} }
} footer: { } footer: {
if team.hasRegisteredOnline() { 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 { 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") { Button("OK") {
self.currentWaitingList = nil self.currentWaitingList = nil
self.presentOnlineRegistrationWarning = false
} }
} }
}, message: { }, 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) .navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: { .toolbar(content: {
@ -351,6 +366,8 @@ struct EditingTeamView: View {
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
_checkOnlineRegistrationWarning()
} }
private var _networkErrorMessage: String { private var _networkErrorMessage: String {

@ -118,8 +118,13 @@ struct TeamRowView: View {
var body: some View { var body: some View {
ForEach(team.players()) { player in ForEach(team.players()) { player in
HStack {
if player.registeredOnline {
Image(systemName: "person.badge.shield.checkmark.fill")
}
Text(player.playerLabel()).lineLimit(1).truncationMode(.tail) Text(player.playerLabel()).lineLimit(1).truncationMode(.tail)
} }
} }
} }
} }
}

@ -59,81 +59,6 @@ struct InscriptionManagerView: View {
return self.tournament.tournamentStore 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 { enum SortingMode: Int, Identifiable, CaseIterable {
var id: Int { self.rawValue } var id: Int { self.rawValue }
case registrationDate case registrationDate
@ -455,11 +380,13 @@ struct InscriptionManagerView: View {
Section { Section {
Button("+1 en tableau") { Button("+1 en tableau") {
tournament.addWildCard(1, .bracket) tournament.addWildCard(1, .bracket)
_setHash()
} }
if tournament.groupStageCount > 0 { if tournament.groupStageCount > 0 {
Button("+1 en poules") { Button("+1 en poules") {
tournament.addWildCard(1, .groupStage) tournament.addWildCard(1, .groupStage)
_setHash()
} }
} }
} header: { } header: {
@ -468,6 +395,7 @@ struct InscriptionManagerView: View {
Button("Bloquer une place") { Button("Bloquer une place") {
tournament.addEmptyTeamRegistration(1) tournament.addEmptyTeamRegistration(1)
_setHash()
} }
Divider() Divider()
@ -675,24 +603,20 @@ struct InscriptionManagerView: View {
Section { Section {
ForEach(teams) { team in ForEach(teams) { team in
let teamIndex = team.index(in: sortedTeams) let teamIndex = team.index(in: sortedTeams)
let color: Color? = isImported ? (team.unrankedOrUnknown() ? .logoRed : (team.isImported() == false ? .beige : nil)) : nil
NavigationLink { NavigationLink {
EditingTeamView(team: team) EditingTeamView(team: team)
.environment(tournament) .environment(tournament)
} label: { } label: {
TeamRowView(team: team) TeamRowView(team: team)
if isImported && team.isImported() == false {
Text("ne provient pas du fichier beach-padel").foregroundStyle(.red)
}
} }
.swipeActions(edge: .trailing, allowsFullSwipe: true) { .swipeActions(edge: .trailing, allowsFullSwipe: true) {
if tournament.enableOnlineRegistration == false { if tournament.enableOnlineRegistration == false {
_teamDeleteButtonView(team) _teamDeleteButtonView(team)
} }
} }
.listRowView(isActive: team.hasRegisteredOnline(), color: .master, hideColorVariation: true) .listRowView(isActive: true, color: team.initialRoundColor() ?? tournament.cutLabelColor(index: teamIndex, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count), hideColorVariation: true, backgroundColor: color, alignment: .trailing)
.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)
} }
} header: { } header: {
if filterMode == .all && walkoutTeams.isEmpty == false { if filterMode == .all && walkoutTeams.isEmpty == false {
@ -701,31 +625,11 @@ struct InscriptionManagerView: View {
Text("\(teams.count.formatted()) équipe\(teams.count.pluralSuffix)") Text("\(teams.count.formatted()) équipe\(teams.count.pluralSuffix)")
} }
} footer: { } footer: {
FooterButtonView("Légende") { FooterButtonView("Légende des codes couleurs") {
showLegendView = true showLegendView = true
} }
} }
.headerProminence(.increased) .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 { } else {
ForEach(teams) { team in ForEach(teams) { team in
let teamIndex = team.index(in: sortedTeams) 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")) .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites"))
.keyboardType(.alphabet) .keyboardType(.alphabet)
.autocorrectionDisabled() .autocorrectionDisabled()
.sheet(isPresented: $showLegendView) {
InscriptionLegendView()
}
} }
@ViewBuilder @ViewBuilder

@ -24,6 +24,7 @@ struct RegistrationSetupView: View {
@State private var licenseIsRequired: Bool @State private var licenseIsRequired: Bool
@State private var minPlayerPerTeam: Int @State private var minPlayerPerTeam: Int
@State private var maxPlayerPerTeam: Int @State private var maxPlayerPerTeam: Int
@State private var showMoreInfos: Bool = false
@State private var hasChanges: Bool = false @State private var hasChanges: Bool = false
@ -78,7 +79,14 @@ struct RegistrationSetupView: View {
Text("Activer") Text("Activer")
} }
} footer: { } footer: {
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.") 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 { if enableOnlineRegistration {
@ -191,6 +199,9 @@ struct RegistrationSetupView: View {
) )
} }
} }
.sheet(isPresented: $showMoreInfos) {
RegistrationInfoSheetView()
}
.toolbar(content: { .toolbar(content: {
if hasChanges { if hasChanges {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {

@ -14,6 +14,8 @@ struct TournamentView: View {
@Environment(NavigationViewModel.self) var navigation: NavigationViewModel @Environment(NavigationViewModel.self) var navigation: NavigationViewModel
@State var tournament: Tournament @State var tournament: Tournament
@State private var showMoreInfos: Bool = false
var presentationContext: PresentationContext = .agenda var presentationContext: PresentationContext = .agenda
let tournamentSelectionTip: TournamentSelectionTip = TournamentSelectionTip() let tournamentSelectionTip: TournamentSelectionTip = TournamentSelectionTip()
@ -62,8 +64,17 @@ struct TournamentView: View {
} }
case .initial: case .initial:
if tournament.enableOnlineRegistration == false { if tournament.enableOnlineRegistration == false {
TipView(onlineRegistrationTip) { actions in TipView(onlineRegistrationTip) { action in
if let actionKey = OnlineRegistrationTip.ActionKey(rawValue: action.id) {
switch actionKey {
case .enableOnlineRegistration:
navigation.path.append(Screen.settings) navigation.path.append(Screen.settings)
case .more:
self.showMoreInfos = true
}
} else {
print("Unknown action: \(action.id)")
}
} }
.tipStyle(tint: .master, asSection: true) .tipStyle(tint: .master, asSection: true)
} }
@ -74,10 +85,19 @@ struct TournamentView: View {
TournamentInitView(tournament: tournament) TournamentInitView(tournament: tournament)
case .build: case .build:
if tournament.enableOnlineRegistration == false { if tournament.enableOnlineRegistration == false {
TipView(onlineRegistrationTip) { actions in TipView(onlineRegistrationTip) { action in
if let actionKey = OnlineRegistrationTip.ActionKey(rawValue: action.id) {
switch actionKey {
case .enableOnlineRegistration:
navigation.path.append(Screen.settings) 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 { Section {
@ -101,6 +121,9 @@ struct TournamentView: View {
TournamentRunningView(tournament: tournament) TournamentRunningView(tournament: tournament)
} }
} }
.sheet(isPresented: $showMoreInfos) {
RegistrationInfoSheetView()
}
} }
.environment(tournament) .environment(tournament)
.id(tournament.id) .id(tournament.id)

@ -11,10 +11,11 @@ struct ListRowViewModifier: ViewModifier {
let isActive: Bool let isActive: Bool
let color: Color let color: Color
var hideColorVariation: Bool = false var hideColorVariation: Bool = false
var backgroundColor: Color? = nil
let alignment: Alignment let alignment: Alignment
func colorVariation() -> Color { func colorVariation() -> Color {
hideColorVariation ? Color(uiColor: .systemBackground) : color.variation() hideColorVariation ? (backgroundColor ?? Color(uiColor: .systemBackground)) : color.variation()
} }
func body(content: Content) -> some View { func body(content: Content) -> some View {
@ -33,7 +34,7 @@ struct ListRowViewModifier: ViewModifier {
} }
extension View { extension View {
func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false, alignment: Alignment = .leading) -> some View { 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, alignment: alignment)) modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation, backgroundColor: backgroundColor, alignment: alignment))
} }
} }

@ -242,7 +242,7 @@ final class ServerDataTests: XCTestCase {
return 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) let pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration)
assert(pr.teamRegistration == playerRegistration.teamRegistration) assert(pr.teamRegistration == playerRegistration.teamRegistration)
@ -264,6 +264,7 @@ final class ServerDataTests: XCTestCase {
assert(pr.hasArrived == playerRegistration.hasArrived) assert(pr.hasArrived == playerRegistration.hasArrived)
assert(pr.captain == playerRegistration.captain) assert(pr.captain == playerRegistration.captain)
assert(pr.coach == playerRegistration.coach) assert(pr.coach == playerRegistration.coach)
assert(pr.registeredOnline == playerRegistration.registeredOnline)
} }

Loading…
Cancel
Save