diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index d387608..cde54b3 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1919,7 +1919,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1957,7 +1957,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 9ea7d91..ec95afd 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -48,8 +48,6 @@ class Tournament : ModelObject, Storable { var publishSummons: Bool = false var publishGroupStages: Bool = false var publishBrackets: Bool = false - - //local var shouldVerifyGroupStage: Bool = false var shouldVerifyBracket: Bool = false @@ -94,9 +92,11 @@ class Tournament : ModelObject, Storable { case _publishSummons = "publishSummons" case _publishGroupStages = "publishGroupStages" case _publishBrackets = "publishBrackets" + case _shouldVerifyGroupStage = "shouldVerifyGroupStage" + case _shouldVerifyBracket = "shouldVerifyBracket" } - 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) { + 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) { self.event = event self.name = name self.startDate = startDate @@ -128,6 +128,8 @@ class Tournament : ModelObject, Storable { self.publishSummons = publishSummons self.publishBrackets = publishBrackets self.publishGroupStages = publishGroupStages + self.shouldVerifyBracket = shouldVerifyBracket + self.shouldVerifyGroupStage = shouldVerifyGroupStage } required init(from decoder: Decoder) throws { @@ -166,7 +168,8 @@ class Tournament : ModelObject, Storable { publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false - + shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false + shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false } fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() @@ -276,6 +279,8 @@ class Tournament : ModelObject, Storable { try container.encode(publishSummons, forKey: ._publishSummons) try container.encode(publishBrackets, forKey: ._publishBrackets) try container.encode(publishGroupStages, forKey: ._publishGroupStages) + try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket) + try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) } fileprivate func _encodePayment(container: inout KeyedEncodingContainer) throws { @@ -1152,6 +1157,15 @@ class Tournament : ModelObject, Storable { return TournamentStatus(label: label, completion: completionLabel) } + func confirmedSummonStatus() -> TournamentStatus { + let selectedSortedTeams = selectedSortedTeams() + let called = selectedSortedTeams.filter { $0.confirmationDate != nil } + let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées" + let completion = (Double(called.count) / Double(selectedSortedTeams.count)) + let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) + return TournamentStatus(label: label, completion: completionLabel) + } + func bracketStatus() -> String { let availableSeeds = availableSeeds() if availableSeeds.isEmpty == false { diff --git a/PadelClub/Utils/ContactManager.swift b/PadelClub/Utils/ContactManager.swift index 7557a7a..229e8c7 100644 --- a/PadelClub/Utils/ContactManager.swift +++ b/PadelClub/Utils/ContactManager.swift @@ -80,7 +80,7 @@ extension ContactType { [entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n\n") } - var intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes" + let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes" if let tournament { return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.short)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)" diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 1c55364..fc17843 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -32,6 +32,11 @@ struct GroupStageSettingsView: View { Section { RowButtonView("Valider les poules", role: .destructive) { tournament.shouldVerifyGroupStage = false + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } } } footer: { Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.") diff --git a/PadelClub/Views/Player/Components/PlayerPopoverView.swift b/PadelClub/Views/Player/Components/PlayerPopoverView.swift index 18974c1..3ceb4a4 100644 --- a/PadelClub/Views/Player/Components/PlayerPopoverView.swift +++ b/PadelClub/Views/Player/Components/PlayerPopoverView.swift @@ -32,7 +32,6 @@ struct PlayerPopoverView: View { @State private var source: String? init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [.firstName, .lastName], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) { - let source = source if let source { let words = source.components(separatedBy: .whitespaces) if words.isEmpty == false { @@ -44,6 +43,8 @@ struct PlayerPopoverView: View { } else { _firstName = State(wrappedValue: source) } + + _source = State(wrappedValue: source) } _sex = State(wrappedValue: sex) @@ -51,7 +52,6 @@ struct PlayerPopoverView: View { self.requiredField = requiredField self.creationCompletionHandler = creationCompletionHandler - _source = State(wrappedValue: source) } var body: some View { @@ -94,6 +94,7 @@ struct PlayerPopoverView: View { Spacer() TextField("Prénom", text: $firstName) .submitLabel(.next) + .autocorrectionDisabled() .keyboardType(.alphabet) .textInputAutocapitalization(.words) .focused($firstNameIsFocused) @@ -108,6 +109,7 @@ struct PlayerPopoverView: View { Spacer() TextField("Nom", text: $lastName) .submitLabel(.next) + .autocorrectionDisabled() .textInputAutocapitalization(.words) .keyboardType(.alphabet) .focused($lastNameIsFocused) @@ -180,7 +182,6 @@ struct PlayerPopoverView: View { .onAppear { firstNameIsFocused = true } - .autocorrectionDisabled() .navigationTitle(sex == 1 ? "Nouveau joueur" : "Nouvelle joueuse") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) @@ -199,23 +200,27 @@ struct PlayerPopoverView: View { } } - if amountIsFocused || licenseIsFocused { + if licenseIsFocused || amountIsFocused { ToolbarItem(placement: .keyboard) { - Button("Confirmer") { - if licenseIsFocused { - license = license.trimmed - if requiredField.contains(.license) { - if license.isLicenseNumber { - amountIsFocused = true + HStack { + Spacer() + Button("Confirmer") { + if licenseIsFocused { + license = license.trimmed + if requiredField.contains(.license) { + if license.isLicenseNumber { + amountIsFocused = true + } else { + displayWrongLicenceError = true + } } else { - displayWrongLicenceError = true + amountIsFocused = true } } else { - amountIsFocused = true + amountIsFocused = false } - } else { - amountIsFocused = false } + .buttonStyle(.bordered) } } } diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 4d96fad..63907b1 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -46,6 +46,9 @@ struct PlayerDetailView: View { .focused($textFieldIsFocus) } label: { Text("Rang") + if player.rank == nil { + Text("Classement calculé : " + player.computedRank.formatted()) + } } } header: { Text("Classement actuel") diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 59f8eb6..5dc9bc5 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct RoundSettingsView: View { @EnvironmentObject var dataStore: DataStore @@ -18,6 +19,11 @@ struct RoundSettingsView: View { Section { RowButtonView("Valider le tableau", role: .destructive) { tournament.shouldVerifyBracket = false + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } } } footer: { Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.") diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 6f19425..a253c5d 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -120,18 +120,22 @@ struct InscriptionManagerView: View { } } .onAppear { - self.presentationCount += 1 if self.teamsHash == nil { self.teamsHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) } } .onDisappear { - self.presentationCount -= 1 - if self.presentationCount == 0 { - let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) - if let teamsHash { - self.tournament.shouldVerifyBracket = newHash != teamsHash - self.tournament.shouldVerifyGroupStage = newHash != teamsHash + let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) + if let teamsHash, newHash != teamsHash { + self.teamsHash = newHash + if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false { + self.tournament.shouldVerifyBracket = true + self.tournament.shouldVerifyGroupStage = true + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } } } } @@ -161,6 +165,7 @@ struct InscriptionManagerView: View { } .sheet(isPresented: $presentPlayerCreation) { PlayerPopoverView(source: _searchSource(), sex: _addPlayerSex()) { p in + p.setComputedRank(in: tournament) createdPlayers.insert(p) createdPlayerIds.insert(p.id) } diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index 31235a2..4da5c93 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -96,7 +96,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) + 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) let t = try await Store.main.service().post(tournament) assert(t.event == tournament.event) @@ -130,7 +130,8 @@ final class ServerDataTests: XCTestCase { assert(t.publishSummons == tournament.publishSummons) assert(t.publishGroupStages == tournament.publishGroupStages) assert(t.publishBrackets == tournament.publishBrackets) - + assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket) + assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage) } func testGroupStage() async throws {