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. 75
      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 {
var mails = [email, contactEmail].compactMap({ $0 })
let mails = [email, contactEmail].compactMap({ $0 })
return mails.isEmpty == false && mails.anySatisfy({ $0.isValidEmail() })
}
public func hasMobilePhone() -> Bool {
var phones = [phoneNumber, contactPhoneNumber].compactMap({ $0 })
let phones = [phoneNumber, contactPhoneNumber].compactMap({ $0 })
return phones.isEmpty == false && phones.anySatisfy({ $0.isPhoneNumber() })
}

@ -125,10 +125,16 @@ final public class Tournament: BaseTournament {
public func groupStages(atStep step: Int = 0) -> [GroupStage] {
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)
}
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] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.groupStages.sorted(by: \GroupStage.computedOrder)

@ -213,16 +213,75 @@ public extension String {
// MARK: - FFT Source Importing
public 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}$/
// Patterns for France only
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}$/
}
private func cleanedNumberForValidation() -> String {
// 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
}
// MARK: - Phone Number Validation
func isMobileNumber() -> Bool {
firstMatch(of: RegexStatic.mobileNumber) != 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
}
}
func isPhoneNumber() -> Bool {
firstMatch(of: RegexStatic.phoneNumber) != nil
/// 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 {
@ -231,7 +290,11 @@ public extension String {
normalized = normalized.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// Remove leading country code for France (33) if present
if normalized.hasPrefix("33") {
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") {
normalized = "0" + normalized.dropFirst(4)
}

@ -57,13 +57,14 @@ public enum ContactManagerError: LocalizedError {
public enum SummonType: Int, Identifiable {
case contact
case contactWithoutSignature
case summon
case summonWalkoutFollowUp
case summonErrorFollowUp
public func isRecall() -> Bool {
switch self {
case .contact:
case .contact, .contactWithoutSignature:
return false
case .summon:
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? {
switch self {
case .contact:
return nil
case .contactWithoutSignature:
return "Sans texte par défaut"
case .summon:
return nil
case .summonWalkoutFollowUp:
@ -89,7 +107,7 @@ public enum SummonType: Int, Identifiable {
public func shouldConfirm() -> Bool {
switch self {
case .contact:
case .contact, .contactWithoutSignature:
return false
case .summon:
return true
@ -102,6 +120,8 @@ public enum SummonType: Int, Identifiable {
public func intro() -> String {
switch self {
case .contactWithoutSignature:
return ""
case .contact:
return "Vous êtes"
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 {
if summonType == .contactWithoutSignature {
return ""
}
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
if useFullCustomMessage {

Loading…
Cancel
Save