From 9a8f9f8949370c7a901a48642de338577ec4bb33 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 29 Nov 2024 15:53:15 +0100 Subject: [PATCH] improve online registration --- PadelClub/Data/PlayerRegistration.swift | 37 ++++++++++++- PadelClub/Data/TeamRegistration.swift | 40 +++++++++++++- PadelClub/Data/Tournament.swift | 40 ++++++++++++-- PadelClub/ViewModel/SetDescriptor.swift | 4 ++ .../Match/Components/PlayerBlockView.swift | 28 +++++++--- .../TournamentGeneralSettingsView.swift | 28 +++++++++- .../Screen/InscriptionManagerView.swift | 22 +++++++- .../Screen/RegisrationSetupView.swift | 53 +++++++++++++++++++ .../Screen/TournamentSettingsView.swift | 16 +++++- PadelClubTests/ServerDataTests.swift | 21 ++++++-- 10 files changed, 269 insertions(+), 20 deletions(-) diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index fe6b270..0ffc213 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -55,7 +55,7 @@ final class PlayerRegistration: ModelObject, Storable { } - 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) { + 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) { self.teamRegistration = teamRegistration self.firstName = firstName self.lastName = lastName @@ -74,6 +74,8 @@ final class PlayerRegistration: ModelObject, Storable { self.computedRank = computedRank self.source = source self.hasArrived = hasArrived + self.captain = captain + self.coach = coach } internal init(importedPlayer: ImportedPlayer) { @@ -376,9 +378,40 @@ final class PlayerRegistration: ModelObject, Storable { case _computedRank = "computedRank" case _source = "source" case _hasArrived = "hasArrived" + case _coach = "coach" + case _captain = "captain" } + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Non-optional properties + id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + firstName = try container.decode(String.self, forKey: ._firstName) + lastName = try container.decode(String.self, forKey: ._lastName) + computedRank = try container.decodeIfPresent(Int.self, forKey: ._computedRank) ?? 0 + hasArrived = try container.decodeIfPresent(Bool.self, forKey: ._hasArrived) ?? false + coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false + captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false + + // Optional properties + teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) + licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) + rank = try container.decodeIfPresent(Int.self, forKey: ._rank) + paymentType = try container.decodeIfPresent(PlayerPaymentType.self, forKey: ._paymentType) + sex = try container.decodeIfPresent(PlayerSexType.self, forKey: ._sex) + tournamentPlayed = try container.decodeIfPresent(Int.self, forKey: ._tournamentPlayed) + points = try container.decodeIfPresent(Double.self, forKey: ._points) + clubName = try container.decodeIfPresent(String.self, forKey: ._clubName) + ligueName = try container.decodeIfPresent(String.self, forKey: ._ligueName) + assimilation = try container.decodeIfPresent(String.self, forKey: ._assimilation) + phoneNumber = try container.decodeIfPresent(String.self, forKey: ._phoneNumber) + email = try container.decodeIfPresent(String.self, forKey: ._email) + birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) + source = try container.decodeIfPresent(PlayerDataSource.self, forKey: ._source) + } + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -402,6 +435,8 @@ final class PlayerRegistration: ModelObject, Storable { try container.encode(computedRank, forKey: ._computedRank) try container.encode(source, forKey: ._source) try container.encode(hasArrived, forKey: ._hasArrived) + try container.encode(captain, forKey: ._captain) + try container.encode(coach, forKey: ._coach) } enum PlayerDataSource: Int, Codable { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 94cd724..aeb2701 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -54,7 +54,7 @@ final class TeamRegistration: ModelObject, Storable { walkOut || unregistered } - init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false) { + init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false, finalRanking: Int? = nil, pointsEarned: Int? = nil, unregistered: Bool = false, unregistrationDate: Date? = nil) { self.tournament = tournament self.groupStage = groupStage self.registrationDate = registrationDate ?? Date() @@ -73,6 +73,10 @@ final class TeamRegistration: ModelObject, Storable { self.lockedWeight = lockedWeight self.confirmationDate = confirmationDate self.qualified = qualified + self.finalRanking = finalRanking + self.pointsEarned = pointsEarned + self.unregistered = unregistered + self.unregistrationDate = unregistrationDate } var tournamentStore: TournamentStore { @@ -636,6 +640,38 @@ final class TeamRegistration: ModelObject, Storable { case _qualified = "qualified" case _finalRanking = "finalRanking" case _pointsEarned = "pointsEarned" + case _unregistered = "unregistered" + case _unregistrationDate = "unregistrationDate" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + // Non-optional properties + id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() + tournament = try container.decode(String.self, forKey: ._tournament) + walkOut = try container.decodeIfPresent(Bool.self, forKey: ._walkOut) ?? false + wildCardBracket = try container.decodeIfPresent(Bool.self, forKey: ._wildCardBracket) ?? false + wildCardGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._wildCardGroupStage) ?? false + weight = try container.decodeIfPresent(Int.self, forKey: ._weight) ?? 0 + qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false + unregistered = try container.decodeIfPresent(Bool.self, forKey: ._unregistered) ?? false + + // Optional properties + groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) + registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) + callDate = try container.decodeIfPresent(Date.self, forKey: ._callDate) + bracketPosition = try container.decodeIfPresent(Int.self, forKey: ._bracketPosition) + groupStagePosition = try container.decodeIfPresent(Int.self, forKey: ._groupStagePosition) + comment = try container.decodeIfPresent(String.self, forKey: ._comment) + source = try container.decodeIfPresent(String.self, forKey: ._source) + sourceValue = try container.decodeIfPresent(String.self, forKey: ._sourceValue) + logo = try container.decodeIfPresent(String.self, forKey: ._logo) + name = try container.decodeIfPresent(String.self, forKey: ._name) + lockedWeight = try container.decodeIfPresent(Int.self, forKey: ._lockedWeight) + confirmationDate = try container.decodeIfPresent(Date.self, forKey: ._confirmationDate) + finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) + pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) + unregistrationDate = try container.decodeIfPresent(Date.self, forKey: ._unregistrationDate) } func encode(to encoder: Encoder) throws { @@ -662,6 +698,8 @@ final class TeamRegistration: ModelObject, Storable { try container.encode(qualified, forKey: ._qualified) try container.encode(finalRanking, forKey: ._finalRanking) try container.encode(pointsEarned, forKey: ._pointsEarned) + try container.encode(unregistered, forKey: ._unregistered) + try container.encode(unregistrationDate, forKey: ._unregistrationDate) } func insertOnServer() { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 025212a..40a8aeb 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -65,7 +65,13 @@ final class Tournament : ModelObject, Storable { var openingRegistrationDate: Date? = nil var targetTeamCount: Int? = nil var waitingListLimit: Int? = nil - + var accountIsRequired: Bool = true + var licenseIsRequired: Bool = true + var minimumPlayerPerTeam: Int = 2 + var maximumPlayerPerTeam: Int = 2 + var information: String? = nil + var displayEntryFeeInformation: Bool = false + @ObservationIgnored var navigationPath: [Screen] = [] @@ -116,10 +122,16 @@ final class Tournament : ModelObject, Storable { case _loserBracketMode = "loserBracketMode" case _initialSeedRound = "initialSeedRound" case _initialSeedCount = "initialSeedCount" + case _accountIsRequired = "account_is_required" + case _licenseIsRequired = "license_is_required" + case _minimumPlayerPerTeam = "minimum_player_per_team" + case _maximumPlayerPerTeam = "maximum_player_per_team" + case _information = "information" + case _displayEntryFeeInformation = "displayEntryFeeInformation" } - internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0) { + internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil, displayEntryFeeInformation: Bool = false) { self.event = event self.name = name self.startDate = startDate @@ -173,6 +185,13 @@ final class Tournament : ModelObject, Storable { self.loserBracketMode = loserBracketMode self.initialSeedRound = initialSeedRound self.initialSeedCount = initialSeedCount + + self.accountIsRequired = accountIsRequired + self.licenseIsRequired = licenseIsRequired + self.minimumPlayerPerTeam = minimumPlayerPerTeam + self.maximumPlayerPerTeam = maximumPlayerPerTeam + self.information = information + self.displayEntryFeeInformation = displayEntryFeeInformation } @@ -221,6 +240,15 @@ final class Tournament : ModelObject, Storable { loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic initialSeedRound = try container.decodeIfPresent(Int.self, forKey: ._initialSeedRound) ?? 0 initialSeedCount = try container.decodeIfPresent(Int.self, forKey: ._initialSeedCount) ?? 0 + + accountIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._accountIsRequired) ?? true + licenseIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._licenseIsRequired) ?? true + minimumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._minimumPlayerPerTeam) ?? 2 + maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2 + + information = try container.decodeIfPresent(String.self, forKey: ._information) + displayEntryFeeInformation = try container.decodeIfPresent(Bool.self, forKey: ._displayEntryFeeInformation) ?? false + } fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() @@ -309,6 +337,12 @@ final class Tournament : ModelObject, Storable { try container.encode(loserBracketMode, forKey: ._loserBracketMode) try container.encode(initialSeedRound, forKey: ._initialSeedRound) try container.encode(initialSeedCount, forKey: ._initialSeedCount) + try container.encode(accountIsRequired, forKey: ._accountIsRequired) + try container.encode(licenseIsRequired, forKey: ._licenseIsRequired) + try container.encode(minimumPlayerPerTeam, forKey: ._minimumPlayerPerTeam) + try container.encode(maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam) + try container.encode(information, forKey: ._information) + try container.encode(displayEntryFeeInformation, forKey: ._displayEntryFeeInformation) } fileprivate func _encodePayment(container: inout KeyedEncodingContainer) throws { @@ -1019,7 +1053,7 @@ defer { //todo func significantPlayerCount() -> Int { - return 2 + return minimumPlayerPerTeam } func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] { diff --git a/PadelClub/ViewModel/SetDescriptor.swift b/PadelClub/ViewModel/SetDescriptor.swift index eb04555..2d3be78 100644 --- a/PadelClub/ViewModel/SetDescriptor.swift +++ b/PadelClub/ViewModel/SetDescriptor.swift @@ -47,12 +47,16 @@ struct SetDescriptor: Identifiable, Equatable { if let valueTeamOne { if let tieBreakValueTeamOne { return "\(valueTeamOne)-\(tieBreakValueTeamOne)" + } else { + return "\(valueTeamOne)" } } case .two: if let valueTeamTwo { if let tieBreakValueTeamTwo { return "\(valueTeamTwo)-\(tieBreakValueTeamTwo)" + } else { + return "\(valueTeamTwo)" } } } diff --git a/PadelClub/Views/Match/Components/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift index c98a724..d4c8d73 100644 --- a/PadelClub/Views/Match/Components/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -127,12 +127,28 @@ struct PlayerBlockView: View { } else { Divider().frame(width: width).overlay(Color(white: 0.9)) } - Text(string) - .font(.title3) - .frame(maxWidth: 20) - .scaledToFill() - .minimumScaleFactor(0.5) - .lineLimit(1) + + let parts = string.components(separatedBy: "-") + if parts.count == 2, let mainScore = parts.first, let supScore = parts.last { + HStack(spacing: 0) { + Text(mainScore) + .font(.title3) + .frame(maxWidth: 20) + .scaledToFill() + .minimumScaleFactor(0.5) + .lineLimit(1) + Text(supScore) + .font(.caption2) + .baselineOffset(10) + } + } else { + Text(string) + .font(.title3) + .frame(maxWidth: 20) + .scaledToFill() + .minimumScaleFactor(0.5) + .lineLimit(1) + } } } } else if let team { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 7b2f6fe..48ce125 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -13,6 +13,7 @@ struct TournamentGeneralSettingsView: View { @Bindable var tournament: Tournament @State private var tournamentName: String = "" + @State private var tournamentInformation: String = "" @State private var entryFee: Double? = nil @State private var confirmationRequired: Bool = false @State private var presentConfirmation: Bool = false @@ -24,6 +25,7 @@ struct TournamentGeneralSettingsView: View { self.tournament = tournament _loserBracketMode = .init(wrappedValue: tournament.loserBracketMode) _tournamentName = State(wrappedValue: tournament.name ?? "") + _tournamentInformation = State(wrappedValue: tournament.information ?? "") _entryFee = State(wrappedValue: tournament.entryFee) } @@ -41,6 +43,14 @@ struct TournamentGeneralSettingsView: View { Text("Nom du tournoi") } + Section { + TextEditor(text: $tournamentInformation) + .keyboardType(.alphabet) + .focused($focusedField, equals: ._information) + } header: { + Text("Description du tournoi") + } + Section { TournamentDatePickerView() TournamentDurationManagerView() @@ -53,6 +63,12 @@ struct TournamentGeneralSettingsView: View { } label: { Text("Inscription") } + + if tournament.isPrivate == false { + Toggle(isOn: $tournament.displayEntryFeeInformation) { + Text("Afficher sur la page d'infos") + } + } } footer: { Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.") } @@ -151,6 +167,13 @@ struct TournamentGeneralSettingsView: View { } else { tournament.name = tournamentName } + } else if focusedField == ._information { + let tournamentInformation = tournamentInformation.prefixTrimmed(4000) + if tournamentInformation.isEmpty { + tournament.information = nil + } else { + tournament.information = tournamentInformation + } } else if focusedField == ._entryFee { tournament.entryFee = entryFee } @@ -167,7 +190,10 @@ struct TournamentGeneralSettingsView: View { .onChange(of: tournament.entryFee) { _save() } - .onChange(of: tournament.name) { + .onChange(of: [tournament.name, tournament.information]) { + _save() + } + .onChange(of: tournament.displayEntryFeeInformation) { _save() } .onChange(of: tournament.dayDuration) { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 8a6e2f4..bce278f 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -72,6 +72,7 @@ struct InscriptionManagerView: View { enum FilterMode: Int, Identifiable, CaseIterable { var id: Int { self.rawValue } case all + case registeredLocally case registeredOnline case walkOut case waiting @@ -92,6 +93,8 @@ struct InscriptionManagerView: View { return "Vous n'avez encore aucune équipe inscrite." case .registeredOnline: return "Aucune équipe inscrite en ligne." + case .registeredLocally: + return "Aucune équipe inscrite par vous-même." case .walkOut: return "Vous n'avez aucune équipe forfait." case .waiting: @@ -115,6 +118,8 @@ struct InscriptionManagerView: View { return "Aucune wildcard en poule" case .all: return "Aucune équipe inscrite" + case .registeredLocally: + return "Aucune équipe inscrite par vous-même" case .registeredOnline: return "Aucune équipe inscrite en ligne" case .walkOut: @@ -140,6 +145,8 @@ struct InscriptionManagerView: View { return displayStyle == .wide ? "Wildcard Poule" : "wc poule" case .all: return displayStyle == .wide ? "Équipes inscrites" : "inscris" + case .registeredLocally: + return displayStyle == .wide ? "Inscrites par vous-même" : "par vous-même" case .registeredOnline: return displayStyle == .wide ? "Inscrites en ligne" : "en ligne" case .bracket: @@ -246,10 +253,12 @@ struct InscriptionManagerView: View { } .refreshable { do { - self.tournament.tournamentStore.teamRegistrations.reset() - try await self.tournament.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed() self.tournament.tournamentStore.playerRegistrations.reset() try await self.tournament.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed() + + self.tournament.tournamentStore.teamRegistrations.reset() + try await self.tournament.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed() + _setHash() } catch { Logger.error(error) } @@ -510,6 +519,12 @@ struct InscriptionManagerView: View { teams = teams.filter({ $0.inGroupStage() }) case .notImported: teams = teams.filter({ $0.isImported() == false }) + case .unregistered: + teams = teams.filter({ $0.hasUnregistered() == true }) + case .registeredLocally: + teams = teams.filter({ $0.hasRegisteredOnline() == false }) + case .registeredOnline: + teams = teams.filter({ $0.hasRegisteredOnline() == true }) default: break } @@ -698,6 +713,9 @@ struct InscriptionManagerView: View { case .unregistered: let unregistered: Int = max(0, sortedTeams.filter({ $0.hasUnregistered() }).count) return unregistered.formatted() + case .registeredLocally: + let registeredLocally: Int = max(0, sortedTeams.filter({ $0.hasRegisteredOnline() == false }).count) + return registeredLocally.formatted() case .registeredOnline: let registeredOnline: Int = max(0, sortedTeams.filter({ $0.hasRegisteredOnline() }).count) return registeredOnline.formatted() diff --git a/PadelClub/Views/Tournament/Screen/RegisrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegisrationSetupView.swift index aa2e266..a7f0713 100644 --- a/PadelClub/Views/Tournament/Screen/RegisrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegisrationSetupView.swift @@ -20,6 +20,11 @@ struct RegisrationSetupView: View { @State private var targetTeamCountEnabled: Bool @State private var waitingListLimitEnabled: Bool @State private var openingRegistrationDateEnabled: Bool + @State private var userAccountIsRequired: Bool + @State private var licenseIsRequired: Bool + @State private var minPlayerPerTeam: Int + @State private var maxPlayerPerTeam: Int + @State private var hasChanges: Bool = false @Environment(\.dismiss) private var dismiss @@ -63,6 +68,12 @@ struct RegisrationSetupView: View { _waitingListLimit = .init(wrappedValue: 0) // Default value _waitingListLimitEnabled = .init(wrappedValue: false) } + + _userAccountIsRequired = .init(wrappedValue: tournament.accountIsRequired) + _licenseIsRequired = .init(wrappedValue: tournament.licenseIsRequired) + _maxPlayerPerTeam = .init(wrappedValue: tournament.maximumPlayerPerTeam) + _minPlayerPerTeam = .init(wrappedValue: tournament.minimumPlayerPerTeam) + } var body: some View { @@ -134,6 +145,29 @@ struct RegisrationSetupView: View { } footer: { Text("Activez et définissez une limite pour la liste d'attente des équipes.") } + + if tournament.isAnimation() { + Section { + Toggle(isOn: $userAccountIsRequired) { + Text("Compte Padel Club requis pour s'inscrire") + } + + Toggle(isOn: $licenseIsRequired) { + Text("Licence FFT requise pour s'inscrire") + } + + LabeledContent { + StepperView(count: $minPlayerPerTeam, minimum: 1, maximum: maxPlayerPerTeam) + } label: { + Text("Nombre minimum de joueurs possible") + } + LabeledContent { + StepperView(count: $maxPlayerPerTeam, minimum: minPlayerPerTeam) + } label: { + Text("Nombre maximum de joueurs possible") + } + } + } } else { ContentUnavailableView( "Activez les inscriptions en ligne", @@ -198,6 +232,13 @@ struct RegisrationSetupView: View { .onChange(of: waitingListLimit) { _hasChanged() } + + .onChange(of: [minPlayerPerTeam, maxPlayerPerTeam]) { + _hasChanged() + } + .onChange(of: [userAccountIsRequired, licenseIsRequired]) { + _hasChanged() + } } private func _hasChanged() { @@ -209,6 +250,18 @@ struct RegisrationSetupView: View { tournament.enableOnlineRegistration = enableOnlineRegistration + if enableOnlineRegistration { + tournament.accountIsRequired = userAccountIsRequired + tournament.licenseIsRequired = licenseIsRequired + tournament.minimumPlayerPerTeam = minPlayerPerTeam + tournament.maximumPlayerPerTeam = maxPlayerPerTeam + } else { + tournament.accountIsRequired = true + tournament.licenseIsRequired = true + tournament.minimumPlayerPerTeam = 2 + tournament.maximumPlayerPerTeam = 2 + } + if openingRegistrationDateEnabled == false { tournament.openingRegistrationDate = nil } else { diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index 5dc380e..15ec869 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -16,6 +16,7 @@ enum TournamentSettings: Identifiable, Selectable, Equatable { case general case club(Tournament) case matchFormats + case onlineRegistration(Tournament) var id: String { String(describing: self) } @@ -29,6 +30,8 @@ enum TournamentSettings: Identifiable, Selectable, Equatable { return "Général" case .club: return "Terrains" + case .onlineRegistration: + return "Inscriptions en ligne" } } @@ -46,6 +49,15 @@ enum TournamentSettings: Identifiable, Selectable, Equatable { } func badgeImage() -> Badge? { + switch self { + case .onlineRegistration(let tournament): + if tournament.enableOnlineRegistration { + return .checkmark + } + default: + return nil + } + return nil } } @@ -55,7 +67,7 @@ struct TournamentSettingsView: View { @Environment(Tournament.self) var tournament: Tournament private func destinations() -> [TournamentSettings] { - [.general, .club(tournament), .matchFormats] + [.general, .club(tournament), .matchFormats, .onlineRegistration(tournament)] } var body: some View { @@ -68,6 +80,8 @@ struct TournamentSettingsView: View { TournamentMatchFormatsSettingsView() case .general: TournamentGeneralSettingsView(tournament: tournament) + case .onlineRegistration: + RegisrationSetupView(tournament: tournament) case .club: TournamentClubSettingsView() } diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index 15887ad..7dc721c 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -100,7 +100,7 @@ final class ServerDataTests: XCTestCase { return } - let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyBracket: true, shouldVerifyGroupStage: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true, loserBracketMode: .manual, initialSeedRound: 8, initialSeedCount: 4) + let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyBracket: true, shouldVerifyGroupStage: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true, loserBracketMode: .manual, initialSeedRound: 8, initialSeedCount: 4, accountIsRequired: false, licenseIsRequired: false, minimumPlayerPerTeam: 3, maximumPlayerPerTeam: 5, information: "Super", displayEntryFeeInformation: true) let t = try await StoreCenter.main.service().post(tournament) assert(t.event == tournament.event) @@ -143,6 +143,12 @@ final class ServerDataTests: XCTestCase { assert(t.loserBracketMode == tournament.loserBracketMode) assert(t.initialSeedCount == tournament.initialSeedCount) assert(t.initialSeedRound == tournament.initialSeedRound) + assert(t.accountIsRequired == tournament.accountIsRequired) + assert(t.licenseIsRequired == tournament.licenseIsRequired) + assert(t.minimumPlayerPerTeam == tournament.minimumPlayerPerTeam) + assert(t.maximumPlayerPerTeam == tournament.maximumPlayerPerTeam) + assert(t.information == tournament.information) + assert(t.displayEntryFeeInformation == tournament.displayEntryFeeInformation) } func testGroupStage() async throws { @@ -203,7 +209,7 @@ final class ServerDataTests: XCTestCase { return } - let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true) + let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true, finalRanking: 4, pointsEarned: 200, unregistered: true, unregistrationDate: Date()) let tr: TeamRegistration = try await StoreCenter.main.service().post(teamRegistration) @@ -225,7 +231,10 @@ final class ServerDataTests: XCTestCase { assert(tr.lockedWeight == teamRegistration.lockedWeight) assert(tr.confirmationDate?.formatted() == teamRegistration.confirmationDate?.formatted()) assert(tr.qualified == teamRegistration.qualified) - + assert(tr.finalRanking == teamRegistration.finalRanking) + assert(tr.pointsEarned == teamRegistration.pointsEarned) + assert(tr.unregistered == teamRegistration.unregistered) + assert(tr.unregistrationDate?.formatted() == teamRegistration.unregistrationDate?.formatted()) } func testPlayerRegistration() async throws { @@ -236,7 +245,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) + 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 pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration) assert(pr.teamRegistration == playerRegistration.teamRegistration) @@ -256,7 +265,9 @@ final class ServerDataTests: XCTestCase { assert(pr.computedRank == playerRegistration.computedRank) assert(pr.source == playerRegistration.source) assert(pr.hasArrived == playerRegistration.hasArrived) - + assert(pr.captain == playerRegistration.captain) + assert(pr.coach == playerRegistration.coach) + } func testMatch() async throws {