small improvements

main
Razmig Sarkissian 3 weeks ago
parent 617bb91d6a
commit dd7207ae33
  1. 4
      PadelClubData/Data/PlayerRegistration.swift
  2. 8
      PadelClubData/Data/Tournament.swift
  3. 81
      PadelClubData/Extensions/String+Extensions.swift
  4. 28
      PadelClubData/Utils/ContactManager.swift

@ -231,12 +231,12 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
} }
public func hasMail() -> Bool { public func hasMail() -> Bool {
var mails = [email, contactEmail].compactMap({ $0 }) let mails = [email, contactEmail].compactMap({ $0 })
return mails.isEmpty == false && mails.anySatisfy({ $0.isValidEmail() }) return mails.isEmpty == false && mails.anySatisfy({ $0.isValidEmail() })
} }
public func hasMobilePhone() -> Bool { public func hasMobilePhone() -> Bool {
var phones = [phoneNumber, contactPhoneNumber].compactMap({ $0 }) let phones = [phoneNumber, contactPhoneNumber].compactMap({ $0 })
return phones.isEmpty == false && phones.anySatisfy({ $0.isPhoneNumber() }) return phones.isEmpty == false && phones.anySatisfy({ $0.isPhoneNumber() })
} }

@ -125,9 +125,15 @@ final public class Tournament: BaseTournament {
public func groupStages(atStep step: Int = 0) -> [GroupStage] { public func groupStages(atStep step: Int = 0) -> [GroupStage] {
guard let tournamentStore = self.tournamentStore else { return [] } guard let tournamentStore = self.tournamentStore else { return [] }
let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.tournament == self.id && $0.step == step } let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.step == step }
return groupStages.sorted(by: \.index) return groupStages.sorted(by: \.index)
} }
public func hasGroupeStages() -> Bool {
if groupStageCount > 0 { return true }
guard let tournamentStore = self.tournamentStore else { return false }
return tournamentStore.groupStages.isEmpty == false
}
public func allGroupStages() -> [GroupStage] { public func allGroupStages() -> [GroupStage] {
guard let tournamentStore = self.tournamentStore else { return [] } guard let tournamentStore = self.tournamentStore else { return [] }

@ -213,25 +213,88 @@ public extension String {
// MARK: - FFT Source Importing // MARK: - FFT Source Importing
public extension String { public extension String {
enum RegexStatic { enum RegexStatic {
static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/ // Patterns for France only
static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/ static let phoneNumber = /^(\+33|0033|33|0)[1-9][0-9]{8}$/
static let phoneNumberWithExtra0 = /^33[0][1-9][0-9]{8}$/
static let mobileNumber = /^(\+33|0033|33|0)[6-7][0-9]{8}$/
static let mobileNumberWithExtra0 = /^33[0][6-7][0-9]{8}$/
} }
func isMobileNumber() -> Bool { private func cleanedNumberForValidation() -> String {
firstMatch(of: RegexStatic.mobileNumber) != nil // Keep leading '+' if present, remove all other non-digit characters
var cleaned = self.trimmingCharacters(in: .whitespacesAndNewlines)
if cleaned.hasPrefix("+") {
// Preserve '+' at start, remove all other non-digit characters
let digitsOnly = cleaned.dropFirst().components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
cleaned = "+" + digitsOnly
} else {
// Remove all non-digit characters
cleaned = cleaned.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
return cleaned
} }
func isPhoneNumber() -> Bool { // MARK: - Phone Number Validation
firstMatch(of: RegexStatic.phoneNumber) != nil
/// Validate if the string is a mobile number for the specified locale.
/// - Parameter locale: The locale to validate against. Defaults to `.current`.
/// - Returns: True if the string matches the mobile number pattern for the locale.
func isMobileNumber(locale: Locale = .current) -> Bool {
// TODO: Support additional regions/locales in the future.
switch locale.region?.identifier {
case "FR", "fr", nil:
// French logic for now
let cleaned = cleanedNumberForValidation()
if cleaned.firstMatch(of: RegexStatic.mobileNumber) != nil {
return true
}
if cleaned.firstMatch(of: RegexStatic.mobileNumberWithExtra0) != nil {
return true
}
return false
default:
// For unsupported locales, fallback to checking if the string contains at least 8 digits
// This is a generic minimum length for most countries' phone numbers
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
return digitsOnly.count >= 8
}
} }
/// Validate if the string is a phone number for the specified locale.
/// - Parameter locale: The locale to validate against. Defaults to `.current`.
/// - Returns: True if the string matches the phone number pattern for the locale.
func isPhoneNumber(locale: Locale = .current) -> Bool {
// TODO: Support additional regions/locales in the future.
switch locale.region?.identifier {
case "FR", "fr", nil:
// French logic for now
let cleaned = cleanedNumberForValidation()
if cleaned.firstMatch(of: RegexStatic.phoneNumber) != nil {
return true
}
if cleaned.firstMatch(of: RegexStatic.phoneNumberWithExtra0) != nil {
return true
}
return false
default:
// For unsupported locales, fallback to checking if the string contains at least 8 digits
// This is a generic minimum length for most countries' phone numbers
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
return digitsOnly.count >= 8
}
}
func normalize(_ phone: String) -> String { func normalize(_ phone: String) -> String {
var normalized = phone.trimmingCharacters(in: .whitespacesAndNewlines) var normalized = phone.trimmingCharacters(in: .whitespacesAndNewlines)
// Remove all non-digit characters // Remove all non-digit characters
normalized = normalized.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() normalized = normalized.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// Remove leading country code for France (33) if present // Remove leading country code for France (33) if present
if normalized.hasPrefix("33") { if normalized.hasPrefix("33") {
normalized = "0" + normalized.dropFirst(2) if normalized.dropFirst(2).hasPrefix("0") {
// Keep as is, don't strip the zero after 33
} else {
normalized = "0" + normalized.dropFirst(2)
}
} else if normalized.hasPrefix("0033") { } else if normalized.hasPrefix("0033") {
normalized = "0" + normalized.dropFirst(4) normalized = "0" + normalized.dropFirst(4)
} }

@ -57,13 +57,14 @@ public enum ContactManagerError: LocalizedError {
public enum SummonType: Int, Identifiable { public enum SummonType: Int, Identifiable {
case contact case contact
case contactWithoutSignature
case summon case summon
case summonWalkoutFollowUp case summonWalkoutFollowUp
case summonErrorFollowUp case summonErrorFollowUp
public func isRecall() -> Bool { public func isRecall() -> Bool {
switch self { switch self {
case .contact: case .contact, .contactWithoutSignature:
return false return false
case .summon: case .summon:
return false return false
@ -74,10 +75,27 @@ public enum SummonType: Int, Identifiable {
} }
} }
public func mainWord() -> String {
switch self {
case .contact:
return "Contacter"
case .contactWithoutSignature:
return "Contacter"
case .summon:
return "Convoquer"
case .summonWalkoutFollowUp:
return "Reconvoquer"
case .summonErrorFollowUp:
return "Reconvoquer"
}
}
public func caption() -> String? { public func caption() -> String? {
switch self { switch self {
case .contact: case .contact:
return nil return nil
case .contactWithoutSignature:
return "Sans texte par défaut"
case .summon: case .summon:
return nil return nil
case .summonWalkoutFollowUp: case .summonWalkoutFollowUp:
@ -89,7 +107,7 @@ public enum SummonType: Int, Identifiable {
public func shouldConfirm() -> Bool { public func shouldConfirm() -> Bool {
switch self { switch self {
case .contact: case .contact, .contactWithoutSignature:
return false return false
case .summon: case .summon:
return true return true
@ -102,6 +120,8 @@ public enum SummonType: Int, Identifiable {
public func intro() -> String { public func intro() -> String {
switch self { switch self {
case .contactWithoutSignature:
return ""
case .contact: case .contact:
return "Vous êtes" return "Vous êtes"
case .summon: case .summon:
@ -161,7 +181,9 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c
} }
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, summonType: SummonType = .summon) -> String { static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, summonType: SummonType = .summon) -> String {
if summonType == .contactWithoutSignature {
return ""
}
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
if useFullCustomMessage { if useFullCustomMessage {

Loading…
Cancel
Save