add contact info

newoffer2025
Razmig Sarkissian 4 months ago
parent 69c0163ccb
commit 331103c4c0
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 59
      PadelClub/Data/Federal/FederalPlayer.swift
  3. 16
      PadelClub/Extensions/MonthData+Extensions.swift
  4. 11
      PadelClub/Extensions/TeamRegistration+Extensions.swift
  5. 59
      PadelClub/Utils/PhoneNumbersUtils.swift
  6. 18
      PadelClub/Views/Calling/Components/MenuWarningView.swift
  7. 20
      PadelClub/Views/Navigation/Umpire/PadelClubView.swift
  8. 103
      PadelClub/Views/Player/PlayerDetailView.swift
  9. 6
      PadelClubTests/ServerDataTests.swift

@ -650,6 +650,9 @@
FF7DCD3A2CC330270041110C /* TeamRestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCD382CC330260041110C /* TeamRestingView.swift */; };
FF7DCD3B2CC330270041110C /* TeamRestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCD382CC330260041110C /* TeamRestingView.swift */; };
FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; };
FF81F1BC2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF81F1BB2E0C4B5F00782CFD /* PhoneNumbersUtils.swift */; };
FF81F1BD2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF81F1BB2E0C4B5F00782CFD /* PhoneNumbersUtils.swift */; };
FF81F1BE2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF81F1BB2E0C4B5F00782CFD /* PhoneNumbersUtils.swift */; };
FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; };
FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; };
FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; };
@ -1058,6 +1061,7 @@
FF77CE592CCCD1FF00CBCBB4 /* GroupStageDatePickingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageDatePickingView.swift; sourceTree = "<group>"; };
FF7DCD382CC330260041110C /* TeamRestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRestingView.swift; sourceTree = "<group>"; };
FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSubscriptionView.swift; sourceTree = "<group>"; };
FF81F1BB2E0C4B5F00782CFD /* PhoneNumbersUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumbersUtils.swift; sourceTree = "<group>"; };
FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = "<group>"; };
FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = "<group>"; };
@ -1935,6 +1939,7 @@
FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */,
FF1DC5582BAB767000FD8220 /* Tips.swift */,
C49C731D2D5E3BE4008DD299 /* VersionComparator.swift */,
FF81F1BB2E0C4B5F00782CFD /* PhoneNumbersUtils.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -2432,6 +2437,7 @@
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FFCB74172C480411008384D0 /* CopyPasteButtonView.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF81F1BD2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */,
FF1F4B712BF9EFE9000B4573 /* TournamentInscriptionView.swift in Sources */,
FF9267FF2BCE94830080F940 /* CallSettingsView.swift in Sources */,
FF025ADD2BD0C94300A86CF8 /* FooterButtonView.swift in Sources */,
@ -2695,6 +2701,7 @@
FF4CC0252C996C0600151637 /* Labels.swift in Sources */,
FF4CC0262C996C0600151637 /* CopyPasteButtonView.swift in Sources */,
FF4CC0272C996C0600151637 /* PlayerSexPickerView.swift in Sources */,
FF81F1BE2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */,
FF4CC0282C996C0600151637 /* TournamentInscriptionView.swift in Sources */,
FF4CC0292C996C0600151637 /* CallSettingsView.swift in Sources */,
FF4CC02A2C996C0600151637 /* FooterButtonView.swift in Sources */,
@ -2936,6 +2943,7 @@
FF70FBA42C90584900129CC2 /* Labels.swift in Sources */,
FF70FBA52C90584900129CC2 /* CopyPasteButtonView.swift in Sources */,
FF70FBA62C90584900129CC2 /* PlayerSexPickerView.swift in Sources */,
FF81F1BC2E0C4B5F00782CFD /* PhoneNumbersUtils.swift in Sources */,
FF70FBA72C90584900129CC2 /* TournamentInscriptionView.swift in Sources */,
FF70FBA82C90584900129CC2 /* CallSettingsView.swift in Sources */,
FF70FBA92C90584900129CC2 /* FooterButtonView.swift in Sources */,
@ -3129,7 +3137,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.40;
MARKETING_VERSION = 1.2.42;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3175,7 +3183,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.40;
MARKETING_VERSION = 1.2.42;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

@ -31,54 +31,70 @@ class FederalPlayer: Decodable {
}
required init(from decoder: Decoder) throws {
/*
"classement": 9,
"evolution": 2,
"nom": "PEREZ LE TIEC",
"prenom": "Pierre",
"meilleurClassement": null,
"nationalite": "FRA",
"ageSportif": 30,
"points": 14210,
"nombreTournoisJoues": 24,
"ligue": "ILE DE FRANCE",
"assimilation": false
*/
enum CodingKeys: String, CodingKey {
case nom
case prenom
case licence
case meilleurClassement
case nationnalite
case anneeNaissance
case nationalite
case codeClub
case nomClub
case nomLigue
case rang
case progression
case ligue
case classement
case evolution
case points
case nombreDeTournois
case assimile
case nombreTournoisJoues
case assimilation
case ageSportif
}
let container = try decoder.container(keyedBy: CodingKeys.self)
isMale = (decoder.userInfo[.maleData] as? Bool) == true
let _lastName = try container.decode(String.self, forKey: .nom)
let _firstName = try container.decode(String.self, forKey: .prenom)
lastName = _lastName
firstName = _firstName
let _lastName = try container.decodeIfPresent(String.self, forKey: .nom)
let _firstName = try container.decodeIfPresent(String.self, forKey: .prenom)
lastName = _lastName ?? ""
firstName = _firstName ?? ""
if let lic = try? container.decodeIfPresent(Int.self, forKey: .licence) {
license = String(lic)
} else {
license = ""
}
let nationnalite = try container.decode(Nationnalite.self, forKey: .nationnalite)
country = nationnalite.code
country = try container.decodeIfPresent(String.self, forKey: .nationalite) ?? ""
bestRank = try container.decodeIfPresent(Int.self, forKey: .meilleurClassement)
birthYear = try container.decodeIfPresent(Int.self, forKey: .anneeNaissance)
clubCode = try container.decode(String.self, forKey: .codeClub)
club = try container.decode(String.self, forKey: .nomClub)
ligue = try container.decode(String.self, forKey: .nomLigue)
rank = try container.decode(Int.self, forKey: .rang)
progression = (try? container.decodeIfPresent(Int.self, forKey: .progression)) ?? 0
let ageSportif = try container.decodeIfPresent(Int.self, forKey: .ageSportif)
if let ageSportif {
birthYear = Calendar.current.component(.year, from: Date()) - ageSportif
}
clubCode = try container.decodeIfPresent(String.self, forKey: .codeClub) ?? ""
club = try container.decodeIfPresent(String.self, forKey: .nomClub) ?? ""
ligue = try container.decodeIfPresent(String.self, forKey: .ligue) ?? ""
rank = try container.decode(Int.self, forKey: .classement)
progression = (try? container.decodeIfPresent(Int.self, forKey: .evolution)) ?? 0
let pointsAsInt = try? container.decodeIfPresent(Int.self, forKey: .points)
if let pointsAsInt {
points = Double(pointsAsInt)
} else {
points = nil
}
tournamentCount = try? container.decodeIfPresent(Int.self, forKey: .nombreDeTournois)
let assimile = try container.decode(Bool.self, forKey: .assimile)
tournamentCount = try? container.decodeIfPresent(Int.self, forKey: .nombreTournoisJoues)
let assimile = try container.decode(Bool.self, forKey: .assimilation)
assimilation = assimile ? "Oui" : "Non"
}
@ -92,6 +108,7 @@ class FederalPlayer: Decodable {
}
func formatNumbers(_ input: String) -> String {
if input.isEmpty { return input }
// Insert spaces at appropriate positions
let formattedString = insertSeparator(input, separator: " ", every: [2, 4])
return formattedString

@ -15,13 +15,22 @@ extension MonthData {
let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == fromDate && $0.index == 0 })
print("calculateCurrentUnrankedValues", fromDate.monthYearFormatted, fileURL?.path())
let fftImportingUncomplete = fileURL?.fftImportingUncomplete()
var fftImportingAnonymous = fileURL?.fftImportingAnonymous()
let fftImportingMaleUnrankValue = fileURL?.fftImportingMaleUnrankValue()
let femaleFileURL = SourceFileManager.shared.allFiles(false).first(where: { $0.dateFromPath == fromDate && $0.index == 0 })
let femaleFftImportingMaleUnrankValue = femaleFileURL?.fftImportingMaleUnrankValue()
let femaleFftImportingUncomplete = femaleFileURL?.fftImportingUncomplete()
let incompleteMode = fftImportingUncomplete != nil
let lastDataSourceMaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: true)
let lastDataSourceFemaleUnranked = await FederalPlayer.lastRank(mostRecentDateAvailable: fromDate, man: false)
let anonymousCount = await FederalPlayer.anonymousCount(mostRecentDateAvailable: fromDate)
if fftImportingAnonymous == nil {
fftImportingAnonymous = await FederalPlayer.anonymousCount(mostRecentDateAvailable: fromDate)
}
let anonymousCount: Int? = fftImportingAnonymous
await MainActor.run {
let lastDataSource = URL.importDateFormatter.string(from: fromDate)
let currentMonthData : MonthData = DataStore.shared.monthData.first(where: { $0.monthKey == lastDataSource }) ?? MonthData(monthKey: lastDataSource)
@ -30,11 +39,10 @@ extension MonthData {
currentMonthData.maleUnrankedValue = incompleteMode ? fftImportingMaleUnrankValue : lastDataSourceMaleUnranked?.0
currentMonthData.incompleteMode = incompleteMode
currentMonthData.maleCount = incompleteMode ? fftImportingUncomplete : lastDataSourceMaleUnranked?.1
currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0
currentMonthData.femaleCount = lastDataSourceFemaleUnranked?.1
currentMonthData.femaleUnrankedValue = incompleteMode ? femaleFftImportingMaleUnrankValue : lastDataSourceFemaleUnranked?.0
currentMonthData.femaleCount = incompleteMode ? femaleFftImportingUncomplete : lastDataSourceFemaleUnranked?.1
currentMonthData.anonymousCount = anonymousCount
DataStore.shared.monthData.addOrUpdate(instance: currentMonthData)
}
}

@ -39,6 +39,17 @@ extension TeamRegistration {
player.licenceId?.strippedLicense != nil
{
player.registeredOnline = oldPlayer.registeredOnline
if player.email?.canonicalVersion != oldPlayer.email?.canonicalVersion {
player.contactEmail = oldPlayer.email
} else {
player.contactEmail = oldPlayer.contactEmail
}
if areFrenchPhoneNumbersSimilar(player.phoneNumber, oldPlayer.phoneNumber) == false {
player.contactPhoneNumber = oldPlayer.phoneNumber
} else {
player.contactPhoneNumber = oldPlayer.contactPhoneNumber
}
player.contactName = oldPlayer.contactName
player.coach = oldPlayer.coach
player.tournamentPlayed = oldPlayer.tournamentPlayed
player.points = oldPlayer.points

@ -0,0 +1,59 @@
import Foundation
func areFrenchPhoneNumbersSimilar(_ phoneNumber1: String?, _ phoneNumber2: String?) -> Bool {
if phoneNumber1?.canonicalVersion == phoneNumber2?.canonicalVersion {
return true
}
// Helper function to normalize a phone number, now returning an optional String
func normalizePhoneNumber(_ numberString: String?) -> String? {
// 1. Safely unwrap the input string. If it's nil or empty, return nil immediately.
guard let numberString = numberString, !numberString.isEmpty else {
return nil
}
// 2. Remove all non-digit characters
let digitsOnly = numberString.filter(\.isNumber)
// If after filtering, there are no digits, return nil.
guard !digitsOnly.isEmpty else {
return nil
}
// 3. Handle French specific prefixes and extract the relevant part
// We need at least 9 digits to get a meaningful 8-digit comparison from the end
if digitsOnly.count >= 9 {
if digitsOnly.hasPrefix("0") {
return String(digitsOnly.suffix(9))
} else if digitsOnly.hasPrefix("33") {
// Ensure there are enough digits after dropping "33"
if digitsOnly.count >= 11 { // "33" + 9 digits = 11
return String(digitsOnly.dropFirst(2).suffix(9))
} else {
return nil // Not enough digits after dropping "33"
}
} else if digitsOnly.count == 9 { // Case like 612341234
return digitsOnly
} else { // More digits but no 0 or 33 prefix, take the last 9
return String(digitsOnly.suffix(9))
}
}
return nil // If it doesn't fit the expected patterns or is too short
}
// Normalize both phone numbers. If either results in nil, we can't compare.
guard let normalizedNumber1 = normalizePhoneNumber(phoneNumber1),
let normalizedNumber2 = normalizePhoneNumber(phoneNumber2) else {
return false
}
// Ensure both normalized numbers have at least 8 digits before comparing suffixes
guard normalizedNumber1.count >= 8 && normalizedNumber2.count >= 8 else {
return false // One or both numbers are too short to have 8 comparable digits
}
// Compare the last 8 digits
return normalizedNumber1.suffix(8) == normalizedNumber2.suffix(8)
}

@ -69,6 +69,13 @@ struct MenuWarningView: View {
Label("Appeler", systemImage: "phone")
Text(number)
}
if let contactPhoneNumber = player.contactPhoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(contactPhoneNumber)") {
Link(destination: url) {
Label("Appeler", systemImage: "phone")
Text(contactPhoneNumber)
}
}
} else {
Menu {
ForEach(players) { player in
@ -78,6 +85,12 @@ struct MenuWarningView: View {
Text(number)
}
}
if let number = player.contactPhoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label(player.playerLabel(.short), systemImage: "phone")
Text(number)
}
}
}
} label: {
Text("Appeler un joueur")
@ -93,12 +106,13 @@ struct MenuWarningView: View {
}
fileprivate func _contactByMessage(players: [PlayerRegistration], privateMode: Bool) {
self.savedContactType = .message(date: date, recipients: players.compactMap({ $0.phoneNumber }), body: message, tournamentBuild: nil)
self.savedContactType = .message(date: date, recipients: players.flatMap({ [$0.phoneNumber, $0.contactPhoneNumber].compacted() }), body: message, tournamentBuild: nil)
self._tryToContact()
}
fileprivate func _contactByMail(players: [PlayerRegistration], privateMode: Bool) {
self.savedContactType = .mail(date: date, recipients: privateMode ? _getUmpireMail() : players.compactMap({ $0.email }), bccRecipients: privateMode ? players.compactMap({ $0.email }) : nil, body: message, subject: subject, tournamentBuild: nil)
let mails = players.flatMap({ [$0.email, $0.contactEmail].compacted() })
self.savedContactType = .mail(date: date, recipients: privateMode ? _getUmpireMail() : mails, bccRecipients: privateMode ? mails : nil, body: message, subject: subject, tournamentBuild: nil)
self._tryToContact()
}

@ -275,6 +275,14 @@ struct PadelClubView: View {
}
}
struct RankingJSON: Decodable {
enum CodingKeys: String, CodingKey {
case joueurs
}
let joueurs: [FederalPlayer]
}
private func _exportCsv() async {
for fileURL in SourceFileManager.shared.jsonFiles() {
let decoder = JSONDecoder()
@ -282,18 +290,16 @@ struct PadelClubView: View {
do {
let data = try Data(contentsOf: fileURL)
let players = try decoder.decode([FederalPlayer].self, from: data)
let players = try decoder.decode(RankingJSON.self, from: data).joueurs
var anonymousPlayers = players.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }
let okPlayers = players.filter { $0.firstName.isEmpty == false && $0.lastName.isEmpty == false }
print("before anonymousPlayers.count", anonymousPlayers.count)
FileImportManager.shared.updatePlayers(isMale: fileURL.manData, players: &anonymousPlayers)
print("after local anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count)
//FileImportManager.shared.updatePlayers(isMale: fileURL.manData, players: &anonymousPlayers)
// print("after local anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count)
await fetchPlayersDataSequentially(for: &anonymousPlayers)
//await fetchPlayersDataSequentially(for: &anonymousPlayers)
print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }
.count)
// print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count)
SourceFileManager.shared.exportToCSV(players: players, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath)
SourceFileManager.shared.exportToCSV("anonymes", players: anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath)
} catch {

@ -18,6 +18,12 @@ struct PlayerDetailView: View {
@State private var licenceId: String
@State private var phoneNumber: String
@State private var email: String
@State private var contactName: String
@State private var contactPhoneNumber: String
@State private var contactEmail: String
@FocusState var focusedField: PlayerRegistration.CodingKeys?
var tournamentStore: TournamentStore? {
@ -29,6 +35,9 @@ struct PlayerDetailView: View {
_licenceId = .init(wrappedValue: player.licenceId ?? "")
_email = .init(wrappedValue: player.email ?? "")
_phoneNumber = .init(wrappedValue: player.phoneNumber ?? "")
_contactName = .init(wrappedValue: player.contactName ?? "")
_contactEmail = .init(wrappedValue: player.contactEmail ?? "")
_contactPhoneNumber = .init(wrappedValue: player.contactPhoneNumber ?? "")
}
var body: some View {
@ -210,6 +219,18 @@ struct PlayerDetailView: View {
}
} label: {
Menu {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: "") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("\(number)", systemImage: "phone")
}
}
if let url = URL(string: "sms:\(number)") {
Link(destination: url) {
Label("\(number)", systemImage: "message")
}
}
Divider()
}
CopyPasteButtonView(pasteValue: player.phoneNumber)
PasteButtonView(text: $phoneNumber)
} label: {
@ -231,34 +252,104 @@ struct PlayerDetailView: View {
}
} label: {
Menu {
if let mail = player.email, let mailURL = URL(string: "mail:\(mail)") {
Link(destination: mailURL) {
Label(mail, systemImage: "mail")
}
Divider()
}
CopyPasteButtonView(pasteValue: player.email)
PasteButtonView(text: $email)
} label: {
Text("Email")
}
}
} header: {
Text("Information fédérale")
}
Section {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: "") {
if let url = URL(string: "tel:\(number)") {
LabeledContent {
TextField("Contact/tuteur", text: $contactName)
.focused($focusedField, equals: ._contactName)
.keyboardType(.alphabet)
.textContentType(nil)
.multilineTextAlignment(.trailing)
.autocorrectionDisabled()
.frame(maxWidth: .infinity)
.onSubmit(of: .text) {
player.contactName = contactName.prefixTrimmed(200)
_save()
}
} label: {
Text("Contact/tuteur")
}
LabeledContent {
TextField("Téléphone contact", text: $contactPhoneNumber)
.focused($focusedField, equals: ._contactPhoneNumber)
.keyboardType(.namePhonePad)
.textContentType(nil)
.multilineTextAlignment(.trailing)
.autocorrectionDisabled()
.frame(maxWidth: .infinity)
.onSubmit(of: .text) {
player.contactPhoneNumber = contactPhoneNumber.prefixTrimmed(50)
_save()
}
} label: {
Menu {
if let number = player.contactPhoneNumber?.replacingOccurrences(of: " ", with: "") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler", systemImage: "phone")
Label("\(number)", systemImage: "phone")
}
}
if let url = URL(string: "sms:\(number)") {
Link(destination: url) {
Label("Message", systemImage: "message")
Label(number, systemImage: "message")
}
}
Divider()
}
if let mail = player.email, let mailURL = URL(string: "mail:\(mail)") {
CopyPasteButtonView(pasteValue: player.contactPhoneNumber)
PasteButtonView(text: $contactPhoneNumber)
} label: {
Text("Téléphone contact")
}
}
LabeledContent {
TextField("Email contact", text: $contactEmail)
.focused($focusedField, equals: ._contactEmail)
.keyboardType(.emailAddress)
.textContentType(nil)
.multilineTextAlignment(.trailing)
.autocorrectionDisabled()
.frame(maxWidth: .infinity)
.onSubmit(of: .text) {
player.contactEmail = contactEmail.prefixTrimmed(50)
_save()
}
} label: {
Menu {
if let mail = player.contactEmail, let mailURL = URL(string: "mail:\(mail)") {
Link(destination: mailURL) {
Label("Mail", systemImage: "mail")
Label(mail, systemImage: "mail")
}
Divider()
}
CopyPasteButtonView(pasteValue: player.contactEmail)
PasteButtonView(text: $contactEmail)
} label: {
Text("Email contact")
}
}
} header: {
Text("Information de contact")
} footer: {
Text("Ces champs vous permettent de garder les informations de contacts avec le joueur s'ils sont différents de ceux renvoyées par la base fédérale. Cela permet également de garder le contact d'un parent s'il s'agit d'un tournoi enfant.")
}
// Section {
// NavigationLink {

@ -310,7 +310,7 @@ final class ServerDataTests: XCTestCase {
return
}
let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerPaymentType.cash, sex: 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: false, captain: false, registeredOnline: false, timeToConfirm: nil, registrationStatus: PlayerRegistration.RegistrationStatus.waiting, paymentId: nil)
let playerRegistration = PlayerRegistration(teamRegistration: teamRegistrationId, firstName: "juan", lastName: "lebron", licenceId: "123", rank: 11, paymentType: PlayerPaymentType.cash, sex: 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: false, captain: false, registeredOnline: false, timeToConfirm: nil, registrationStatus: PlayerRegistration.RegistrationStatus.waiting, paymentId: nil, contactName: "coach juan", contactPhoneNumber: "4587654321", contactEmail: "juana@email.com")
playerRegistration.storeId = "123"
if let pr: PlayerRegistration = try await StoreCenter.main.service().post(playerRegistration) {
@ -343,6 +343,10 @@ final class ServerDataTests: XCTestCase {
assert(pr.timeToConfirm == playerRegistration.timeToConfirm)
assert(pr.registrationStatus == playerRegistration.registrationStatus)
assert(pr.paymentId == playerRegistration.paymentId)
assert(pr.contactName == playerRegistration.contactName)
assert(pr.contactEmail == playerRegistration.contactEmail)
assert(pr.contactPhoneNumber == playerRegistration.contactPhoneNumber)
} else {
XCTFail("missing data")
}

Loading…
Cancel
Save