sync_v2
Raz 8 months ago
parent acb0be6ec3
commit 74d6f8ba3c
  1. 12
      PadelClub/Data/CustomUser.swift
  2. 30
      PadelClub/Data/Gen/BaseTournament.swift
  3. 22
      PadelClub/Data/Gen/Tournament.json
  4. 13
      PadelClub/Data/Match.swift
  5. 11
      PadelClub/Data/Tournament.swift
  6. 5
      PadelClub/Extensions/String+Extensions.swift
  7. 4
      PadelClub/Utils/ContactManager.swift
  8. 4
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  9. 2
      PadelClub/Views/Calling/CallView.swift
  10. 2
      PadelClub/Views/Calling/SendToAllView.swift
  11. 2
      PadelClub/Views/Score/EditScoreView.swift
  12. 113
      PadelClub/Views/Tournament/Screen/Components/TournamentCategorySettingsView.swift
  13. 266
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  14. 6
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  15. 2
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift
  16. 9
      PadelClubTests/ServerDataTests.swift

@ -82,15 +82,13 @@ class CustomUser: BaseCustomUser, UserBase {
return try? federalContext.fetch(fetchRequest).first
}
func defaultSignature() -> String {
return "Sportivement,\n\(firstName) \(lastName), votre JAP."
func defaultSignature(_ tournament: Tournament?) -> String {
let fullName = tournament?.umpireCustomContact ?? fullName()
return "Sportivement,\n\(fullName), votre JAP."
}
func fullName() -> String? {
guard firstName.isEmpty == false && lastName.isEmpty == false else {
return nil
}
return "\(firstName) \(lastName)"
func fullName() -> String {
[firstName, lastName].joined(separator: " ")
}
func hasTenupClubs() -> Bool {

@ -64,6 +64,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
var maximumPlayerPerTeam: Int = 2
var information: String? = nil
var umpireCustomMail: String? = nil
var umpireCustomContact: String? = nil
var umpireCustomPhone: String? = nil
var hideUmpireMail: Bool = false
var hideUmpirePhone: Bool = true
var disableRankingFederalRuling: Bool = false
init(
@ -120,6 +124,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
maximumPlayerPerTeam: Int = 2,
information: String? = nil,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false
) {
super.init()
@ -176,6 +184,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.maximumPlayerPerTeam = maximumPlayerPerTeam
self.information = information
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.umpireCustomPhone = umpireCustomPhone
self.hideUmpireMail = hideUmpireMail
self.hideUmpirePhone = hideUmpirePhone
self.disableRankingFederalRuling = disableRankingFederalRuling
}
@ -233,6 +245,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
case _maximumPlayerPerTeam = "maximumPlayerPerTeam"
case _information = "information"
case _umpireCustomMail = "umpireCustomMail"
case _umpireCustomContact = "umpireCustomContact"
case _umpireCustomPhone = "umpireCustomPhone"
case _hideUmpireMail = "hideUmpireMail"
case _hideUmpirePhone = "hideUmpirePhone"
case _disableRankingFederalRuling = "disableRankingFederalRuling"
}
@ -352,6 +368,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2
self.information = try container.decodeIfPresent(String.self, forKey: ._information) ?? nil
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
try super.init(from: decoder)
}
@ -411,6 +431,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
try container.encode(self.maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam)
try container.encode(self.information, forKey: ._information)
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail)
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact)
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone)
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail)
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try super.encode(to: encoder)
}
@ -475,6 +499,10 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.maximumPlayerPerTeam = tournament.maximumPlayerPerTeam
self.information = tournament.information
self.umpireCustomMail = tournament.umpireCustomMail
self.umpireCustomContact = tournament.umpireCustomContact
self.umpireCustomPhone = tournament.umpireCustomPhone
self.hideUmpireMail = tournament.hideUmpireMail
self.hideUmpirePhone = tournament.hideUmpirePhone
self.disableRankingFederalRuling = tournament.disableRankingFederalRuling
}
@ -484,4 +512,4 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
]
}
}
}

@ -270,10 +270,30 @@
"type": "String",
"optional": true
},
{
"name": "umpireCustomContact",
"type": "String",
"optional": true
},
{
"name": "umpireCustomPhone",
"type": "String",
"optional": true
},
{
"name": "hideUmpireMail",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hideUmpirePhone",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "disableRankingFederalRuling",
"type": "Bool",
"defaultValue": false,
"defaultValue": "false",
"optional": false
}
]

@ -522,6 +522,19 @@ defer {
updateFollowingMatchTeamScore()
}
func setUnfinishedScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
updateScore(fromMatchDescriptor: matchDescriptor)
if endDate == nil {
endDate = Date()
}
if startDate == nil {
startDate = endDate?.addingTimeInterval(Double(-getDuration()*60))
} else if let startDate, let endDate, startDate >= endDate {
self.startDate = endDate.addingTimeInterval(Double(-getDuration()*60))
}
confirmed = true
}
func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
updateScore(fromMatchDescriptor: matchDescriptor)
if endDate == nil {

@ -32,6 +32,10 @@ final class Tournament: BaseTournament {
internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, 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, enableOnlineRegistration: Bool = false, registrationDateLimit: Date? = nil, openingRegistrationDate: Date? = nil, waitingListLimit: Int? = nil, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false
) {
super.init()
@ -99,6 +103,7 @@ final class Tournament: BaseTournament {
self.maximumPlayerPerTeam = maximumPlayerPerTeam
self.information = information
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.disableRankingFederalRuling = disableRankingFederalRuling
}
@ -2595,12 +2600,16 @@ extension Tournament {
let disableRankingFederalRuling = tournaments.first?.disableRankingFederalRuling ?? false
let umpireCustomMail = tournaments.first?.umpireCustomMail
let umpireCustomPhone = tournaments.first?.umpireCustomPhone
let umpireCustomContact = tournaments.first?.umpireCustomContact
let hideUmpireMail = tournaments.first?.hideUmpireMail ?? false
let hideUmpirePhone = tournaments.first?.hideUmpirePhone ?? true
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
//creator: DataStore.shared.user?.id
return Tournament(isPrivate: shouldBePrivate, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge, loserBracketMode: DataStore.shared.user.loserBracketMode, umpireCustomMail: umpireCustomMail, disableRankingFederalRuling: disableRankingFederalRuling)
return Tournament(isPrivate: shouldBePrivate, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge, loserBracketMode: DataStore.shared.user.loserBracketMode, umpireCustomMail: umpireCustomMail, umpireCustomContact: umpireCustomContact, umpireCustomPhone: umpireCustomPhone, hideUmpireMail: hideUmpireMail, hideUmpirePhone: hideUmpirePhone, disableRankingFederalRuling: disableRankingFederalRuling)
}
static func fake() -> Tournament {

@ -171,12 +171,17 @@ extension String {
extension String {
enum RegexStatic {
static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/
static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/
}
func isMobileNumber() -> Bool {
firstMatch(of: RegexStatic.mobileNumber) != nil
}
func isPhoneNumber() -> Bool {
firstMatch(of: RegexStatic.phoneNumber) != nil
}
//april 04-2024 bug with accent characters / adobe / fft
mutating func replace(characters: [(Character, Character)]) {
for (targetChar, replacementChar) in characters {

@ -91,7 +91,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))")
text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))")
let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature()
let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature(tournament)
text = text.replacingOccurrences(of: "#signature", with: signature)
return text
@ -109,7 +109,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
let clubName = tournament?.clubName ?? ""
let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature()
let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature(tournament)
let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s"

@ -29,7 +29,7 @@ struct CallMessageCustomizationView: View {
init(tournament: Tournament) {
self.tournament = tournament
_customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? (DataStore.shared.user.summonsUseFullCustomMessage ? "" : ContactType.defaultCustomMessage))
_customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature())
_customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature(tournament))
_customClubName = State(wrappedValue: tournament.clubName ?? "Lieu du tournoi")
_summonsAvailablePaymentMethods = State(wrappedValue: DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods)
}
@ -89,7 +89,7 @@ struct CallMessageCustomizationView: View {
}
Divider()
FooterButtonView("défaut") {
customCallMessageSignature = DataStore.shared.user.defaultSignature()
customCallMessageSignature = DataStore.shared.user.defaultSignature(tournament)
_save()
}
Divider()

@ -139,7 +139,7 @@ struct CallView: View {
func finalMessage(reSummon: Bool, forcedEmptyMessage: Bool) -> String {
if simpleMode || forcedEmptyMessage {
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature()
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature(tournament)
return "\n\n\n\n" + signature
}

@ -261,7 +261,7 @@ struct SendToAllView: View {
message.append(tournament.shareURL(pageLink)?.absoluteString)
}
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature()
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature(tournament)
message.append(signature)

@ -202,7 +202,7 @@ struct EditScoreView: View {
Section {
RowButtonView("Terminer la rencontre") {
matchDescriptor.match?.setScore(fromMatchDescriptor: matchDescriptor)
matchDescriptor.match?.setUnfinishedScore(fromMatchDescriptor: matchDescriptor)
save()
dismiss()
}

@ -10,13 +10,71 @@ import SwiftUI
import LeStorage
struct TournamentCategorySettingsView: View {
@Environment(Tournament.self) private var tournament: Tournament
@Bindable var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
@State private var confirmationRequired: Bool = false
@State private var presentConfirmation: Bool = false
@State private var loserBracketMode: LoserBracketMode
init(tournament: Tournament) {
self.tournament = tournament
_loserBracketMode = .init(wrappedValue: tournament.loserBracketMode)
}
var body: some View {
List {
TournamentLevelPickerView()
Section {
Picker(selection: $loserBracketMode) {
ForEach(LoserBracketMode.allCases) {
Text($0.localizedLoserBracketMode()).tag($0)
}
} label: {
Text("Position des perdants")
}
.onChange(of: loserBracketMode) {
if tournament.allLoserRoundMatches().anySatisfy({ $0.hasEnded() }) == false {
_refreshLoserBracketMode()
} else {
confirmationRequired = true
}
}
} header: {
Text("Matchs de classement")
} footer: {
if confirmationRequired == false {
if dataStore.user.loserBracketMode != tournament.loserBracketMode {
_footerView()
.onTapGesture(perform: {
self.dataStore.user.loserBracketMode = tournament.loserBracketMode
self.dataStore.saveUser()
})
} else {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
}
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
presentConfirmation = true
})
}
}
}
.confirmationDialog("Attention", isPresented: $presentConfirmation, actions: {
Button("Confirmer", role: .destructive) {
_refreshLoserBracketMode()
confirmationRequired = false
}
Button("Annuler", role: .cancel) {
loserBracketMode = tournament.loserBracketMode
}
})
.toolbarBackground(.visible, for: .navigationBar)
.onChange(of: [
tournament.federalCategory,
]) {
@ -32,7 +90,48 @@ struct TournamentCategorySettingsView: View {
]) {
_save()
}
.onChange(of: [
tournament.groupStageSortMode,
]) {
_save()
}
}
private func _refreshLoserBracketMode() {
tournament.loserBracketMode = loserBracketMode
_save()
let rounds = tournament.rounds()
rounds.forEach { round in
let matches = round.loserRoundsAndChildren().flatMap({ $0._matches() })
matches.forEach { match in
match.resetTeamScores(outsideOf: [])
match.resetMatch()
match.confirmed = false
}
round.loserBracketMode = tournament.loserBracketMode
if loserBracketMode == .automatic {
matches.forEach { match in
match.updateTeamScores()
}
}
do {
try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
}
do {
try self.tournament.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
}
private func _save() {
do {
@ -45,4 +144,16 @@ struct TournamentCategorySettingsView: View {
}
}
private func _footerView() -> some View {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
+
Text(" Modifier le réglage par défaut pour tous vos tournois").foregroundStyle(.blue)
}
private func _footerViewConfirmationRequired() -> some View {
Text("Au moins un match de classement est terminé, en modifiant ce réglage, les résultats de ces matchs de classement seront perdus.")
+
Text(" Modifier quand même ?").foregroundStyle(.red)
}
}

@ -15,21 +15,23 @@ struct TournamentGeneralSettingsView: View {
@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
@State private var loserBracketMode: LoserBracketMode
@State private var umpireCustomMail: String
@State private var umpireCustomPhone: String
@State private var umpireCustomContact: String
@State private var umpireCustomMailIsInvalid: Bool = false
@State private var umpireCustomPhoneIsInvalid: Bool = false
@FocusState private var focusedField: Tournament.CodingKeys?
let priceTags: [Double] = [15.0, 20.0, 25.0]
init(tournament: Tournament) {
self.tournament = tournament
_loserBracketMode = .init(wrappedValue: tournament.loserBracketMode)
_tournamentName = State(wrappedValue: tournament.name ?? "")
_tournamentInformation = State(wrappedValue: tournament.information ?? "")
_entryFee = State(wrappedValue: tournament.entryFee)
_umpireCustomMail = State(wrappedValue: tournament.umpireCustomMail ?? "")
_umpireCustomPhone = State(wrappedValue: tournament.umpireCustomPhone ?? "")
_umpireCustomContact = State(wrappedValue: tournament.umpireCustomContact ?? "")
}
var body: some View {
@ -78,6 +80,22 @@ struct TournamentGeneralSettingsView: View {
_customUmpireView()
Section {
if tournament.hideUmpireMail, tournament.hideUmpirePhone, tournament.enableOnlineRegistration {
Text("Attention, les emails envoyés automatiquement au regard des inscriptions en ligne ne contiendront aucun moyen de vous contacter.").foregroundStyle(.logoRed)
}
Toggle(isOn: $tournament.hideUmpireMail) {
Text("Masquer l'email")
}
Toggle(isOn: $tournament.hideUmpirePhone) {
Text("Masquer le téléphone")
}
} footer: {
Text("Ces informations ne seront pas affichées sur la page d'information du tournoi sur Padel Club et dans les emails envoyés automatiquement au regard des inscriptions en lignes.")
}
Section {
TextField("Nom du tournoi", text: $tournamentName, axis: .vertical)
.lineLimit(2)
@ -110,53 +128,7 @@ struct TournamentGeneralSettingsView: View {
tournamentInformation.append("\n" + tournament.entryFeeMessage)
}
}
Section {
Picker(selection: $loserBracketMode) {
ForEach(LoserBracketMode.allCases) {
Text($0.localizedLoserBracketMode()).tag($0)
}
} label: {
Text("Position des perdants")
}
.onChange(of: loserBracketMode) {
if tournament.allLoserRoundMatches().anySatisfy({ $0.hasEnded() }) == false {
_refreshLoserBracketMode()
} else {
confirmationRequired = true
}
}
} header: {
Text("Matchs de classement")
} footer: {
if confirmationRequired == false {
if dataStore.user.loserBracketMode != tournament.loserBracketMode {
_footerView()
.onTapGesture(perform: {
self.dataStore.user.loserBracketMode = tournament.loserBracketMode
self.dataStore.saveUser()
})
} else {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
}
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
presentConfirmation = true
})
}
}
}
.confirmationDialog("Attention", isPresented: $presentConfirmation, actions: {
Button("Confirmer", role: .destructive) {
_refreshLoserBracketMode()
confirmationRequired = false
}
Button("Annuler", role: .cancel) {
loserBracketMode = tournament.loserBracketMode
}
})
.navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: {
if focusedField != nil {
@ -196,14 +168,12 @@ struct TournamentGeneralSettingsView: View {
Button("Effacer") {
tournament.name = nil
tournamentName = ""
_save()
}
.buttonStyle(.borderless)
} else if focusedField == ._information, tournamentInformation.isEmpty == false {
Button("Effacer") {
tournament.information = nil
tournamentInformation = ""
_save()
}
.buttonStyle(.borderless)
} else if focusedField == ._umpireCustomMail, umpireCustomMail.isEmpty == false {
@ -211,29 +181,20 @@ struct TournamentGeneralSettingsView: View {
_deleteUmpireMail()
}
.buttonStyle(.borderless)
} else if focusedField == ._umpireCustomPhone, umpireCustomPhone.isEmpty == false {
Button("Effacer") {
_deleteUmpirePhone()
}
.buttonStyle(.borderless)
} else if focusedField == ._umpireCustomContact, umpireCustomContact.isEmpty == false {
Button("Effacer") {
_deleteUmpireContact()
}
.buttonStyle(.borderless)
}
}
Spacer()
Button("Valider") {
if focusedField == ._name {
let tournamentName = tournamentName.prefixMultilineTrimmed(200)
if tournamentName.isEmpty {
tournament.name = nil
} else {
tournament.name = tournamentName
}
} else if focusedField == ._information {
let tournamentInformation = tournamentInformation.prefixMultilineTrimmed(4000)
if tournamentInformation.isEmpty {
tournament.information = nil
} else {
tournament.information = tournamentInformation
}
} else if focusedField == ._entryFee {
tournament.entryFee = entryFee
} else if focusedField == ._umpireCustomMail {
_confirmUmpireMail()
}
focusedField = nil
}
.buttonStyle(.bordered)
@ -247,15 +208,39 @@ struct TournamentGeneralSettingsView: View {
.onChange(of: tournament.entryFee) {
_save()
}
.onChange(of: [tournament.name, tournament.information]) {
.onChange(of: [tournament.name, tournament.information, tournament.umpireCustomMail, tournament.umpireCustomPhone, tournament.umpireCustomContact]) {
_save()
}
.onChange(of: [tournament.hideUmpireMail, tournament.hideUmpirePhone]) {
_save()
}
.onChange(of: tournament.dayDuration) {
_save()
}
.onChange(of: [
tournament.groupStageSortMode,
]) {
.onChange(of: focusedField) { old, new in
if old == ._name {
let tournamentName = tournamentName.prefixMultilineTrimmed(200)
if tournamentName.isEmpty {
tournament.name = nil
} else {
tournament.name = tournamentName
}
} else if old == ._information {
let tournamentInformation = tournamentInformation.prefixMultilineTrimmed(4000)
if tournamentInformation.isEmpty {
tournament.information = nil
} else {
tournament.information = tournamentInformation
}
} else if old == ._entryFee {
tournament.entryFee = entryFee
} else if old == ._umpireCustomMail {
_confirmUmpireMail()
} else if old == ._umpireCustomPhone {
_confirmUmpirePhone()
} else if old == ._umpireCustomContact {
_confirmUmpireContact()
}
_save()
}
}
@ -264,10 +249,8 @@ struct TournamentGeneralSettingsView: View {
umpireCustomMailIsInvalid = false
if umpireCustomMail.isEmpty {
tournament.umpireCustomMail = nil
_save()
} else if umpireCustomMail.isValidEmail() {
tournament.umpireCustomMail = umpireCustomMail
_save()
} else {
umpireCustomMailIsInvalid = true
}
@ -277,47 +260,41 @@ struct TournamentGeneralSettingsView: View {
umpireCustomMailIsInvalid = false
umpireCustomMail = ""
tournament.umpireCustomMail = nil
_save()
}
private func _save() {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
private func _confirmUmpirePhone() {
umpireCustomPhoneIsInvalid = false
if umpireCustomPhone.isEmpty {
tournament.umpireCustomPhone = nil
} else if umpireCustomPhone.isPhoneNumber() {
tournament.umpireCustomPhone = umpireCustomPhone.prefixMultilineTrimmed(15)
} else {
umpireCustomPhoneIsInvalid = true
}
}
private func _refreshLoserBracketMode() {
tournament.loserBracketMode = loserBracketMode
_save()
let rounds = tournament.rounds()
rounds.forEach { round in
let matches = round.loserRoundsAndChildren().flatMap({ $0._matches() })
matches.forEach { match in
match.resetTeamScores(outsideOf: [])
match.resetMatch()
match.confirmed = false
}
round.loserBracketMode = tournament.loserBracketMode
if loserBracketMode == .automatic {
matches.forEach { match in
match.updateTeamScores()
}
}
do {
try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
private func _deleteUmpirePhone() {
umpireCustomPhoneIsInvalid = false
umpireCustomPhone = ""
tournament.umpireCustomPhone = nil
}
private func _confirmUmpireContact() {
if umpireCustomContact.isEmpty {
tournament.umpireCustomContact = nil
} else {
tournament.umpireCustomContact = umpireCustomContact.prefixMultilineTrimmed(200)
}
}
private func _deleteUmpireContact() {
umpireCustomContact = ""
tournament.umpireCustomContact = nil
}
private func _save() {
do {
try self.tournament.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds)
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
@ -325,32 +302,55 @@ struct TournamentGeneralSettingsView: View {
private func _customUmpireView() -> some View {
Section {
if umpireCustomMailIsInvalid {
Text("Vous n'avez pas indiqué un email valide.").foregroundStyle(.logoRed)
VStack(alignment: .leading) {
TextField(dataStore.user.email, text: $umpireCustomMail)
.frame(maxWidth: .infinity)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.focused($focusedField, equals: ._umpireCustomMail)
.onSubmit {
_confirmUmpireMail()
}
if umpireCustomMailIsInvalid {
Text("Vous n'avez pas indiqué un email valide.").foregroundStyle(.logoRed)
}
}
TextField(dataStore.user.email, text: $umpireCustomMail)
.frame(maxWidth: .infinity)
.keyboardType(.emailAddress)
.focused($focusedField, equals: ._umpireCustomMail)
.onSubmit {
_confirmUmpireMail()
VStack(alignment: .leading) {
TextField(dataStore.user.phone ?? "Téléphone", text: $umpireCustomPhone)
.frame(maxWidth: .infinity)
.keyboardType(.phonePad)
.focused($focusedField, equals: ._umpireCustomPhone)
.onSubmit {
_confirmUmpirePhone()
}
if umpireCustomPhoneIsInvalid {
Text("Vous n'avez pas indiqué un téléphone valide.").foregroundStyle(.logoRed)
}
}
VStack(alignment: .leading) {
TextField(dataStore.user.fullName(), text: $umpireCustomContact)
.frame(maxWidth: .infinity)
.keyboardType(.default)
.focused($focusedField, equals: ._umpireCustomContact)
.onSubmit {
_confirmUmpireContact()
}
if dataStore.user.summonsMessageSignature != nil, umpireCustomContact != dataStore.user.fullName() {
Text("Attention vous avez une signature personnalisée contenant un contact différent.").foregroundStyle(.logoRed)
FooterButtonView("retirer la personnalisation ?") {
dataStore.user.summonsMessageSignature = nil
self.dataStore.saveUser()
}
} }
} header: {
Text("Email du juge-arbitre")
Text("Juge-arbitre")
} footer: {
Text("Cet email sera utilisé pour vous contacter. Vous pouvez le modifier si vous souhaitez utiliser un autre email que celui de votre compte Padel Club.")
Text("Ces informations seront utilisées pour vous contacter. Vous pouvez les modifier si vous souhaitez utiliser les informations de contact différentes de votre compte Padel Club.")
}
}
private func _footerView() -> some View {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
+
Text(" Modifier le réglage par défaut pour tous vos tournois").foregroundStyle(.blue)
}
private func _footerViewConfirmationRequired() -> some View {
Text("Au moins un match de classement est terminé, en modifiant ce réglage, les résultats de ces matchs de classement seront perdus.")
+
Text(" Modifier quand même ?").foregroundStyle(.red)
}
}

@ -74,6 +74,12 @@ struct TournamentRankView: View {
Text("Désactiver la règle fédéral")
Text("Dernier de poule ≠ derner du tournoi")
}
.onChange(of: tournament.disableRankingFederalRuling) {
dataStore.tournaments.addOrUpdate(instance: tournament)
Task {
await _calculateRankings()
}
}
.disabled(calculating)
} footer: {

@ -70,7 +70,7 @@ struct TournamentSettingsView: View {
case .matchFormats:
TournamentMatchFormatsSettingsView()
case .tournamentType:
TournamentCategorySettingsView()
TournamentCategorySettingsView(tournament: tournament)
case .general:
TournamentGeneralSettingsView(tournament: tournament)
case .club:

@ -113,7 +113,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, accountIsRequired: false, licenseIsRequired: false, minimumPlayerPerTeam: 3, maximumPlayerPerTeam: 5, information: "Super")
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", umpireCustomMail: "razmig@padelclub.app", umpireCustomContact: "Raz", umpireCustomPhone: "+33681598193", hideUmpireMail: true, hideUmpirePhone: true, disableRankingFederalRuling: true)
if let t = try await StoreCenter.main.service().post(tournament) {
@ -163,7 +163,12 @@ final class ServerDataTests: XCTestCase {
assert(t.minimumPlayerPerTeam == tournament.minimumPlayerPerTeam)
assert(t.maximumPlayerPerTeam == tournament.maximumPlayerPerTeam)
assert(t.information == tournament.information)
assert(t.umpireCustomMail == tournament.umpireCustomMail)
assert(t.umpireCustomContact == tournament.umpireCustomContact)
assert(t.umpireCustomPhone == tournament.umpireCustomPhone)
assert(t.hideUmpireMail == tournament.hideUmpireMail)
assert(t.hideUmpirePhone == tournament.hideUmpirePhone)
assert(t.disableRankingFederalRuling == tournament.disableRankingFederalRuling)
} else {
XCTFail("missing data")
}

Loading…
Cancel
Save