sync3
Laurent 6 months ago
commit 5d25f21ebb
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/Federal/FederalTournament.swift
  3. 46
      PadelClub/Utils/Network/NetworkFederalService.swift
  4. 9
      PadelClub/Views/Cashier/Event/EventLinksView.swift
  5. 8
      PadelClub/Views/Cashier/Event/EventSettingsView.swift
  6. 99
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  7. 30
      PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift
  8. 2
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  9. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  10. 25
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -3259,7 +3259,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3285,7 +3285,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.21; MARKETING_VERSION = 1.2.22;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3305,7 +3305,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -3330,7 +3330,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.21; MARKETING_VERSION = 1.2.22;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3351,7 +3351,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3374,7 +3374,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.21; MARKETING_VERSION = 1.2.22;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3394,7 +3394,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -3416,7 +3416,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.21; MARKETING_VERSION = 1.2.22;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -253,7 +253,7 @@ struct FederalTournament: Identifiable, Codable {
} }
func umpireLabel() -> String { func umpireLabel() -> String {
[jugeArbitre?.nom, jugeArbitre?.prenom].compactMap({$0}).joined(separator: " ") [jugeArbitre?.nom, jugeArbitre?.prenom].compactMap({$0}).map({ $0.lowercased().capitalized }).joined(separator: " ")
} }
func phoneLabel() -> String { func phoneLabel() -> String {

@ -275,4 +275,50 @@ recherche_type=\(searchType)&ville%5Bautocomplete%5D%5Bcountry%5D=fr&ville%5Baut
return try await runTenupTask(request: request) return try await runTenupTask(request: request)
} }
func getUmpireData(idTournament: String) async throws -> (name: String?, email: String?, phone: String?) {
guard let url = URL(string: "https://tenup.fft.fr/tournoi/\(idTournament)") else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
guard let htmlString = String(data: data, encoding: .utf8) else {
throw URLError(.cannotDecodeContentData)
}
let namePattern = "tournoi-detail-page-inscription-responsable-title\">\\s*([^<]+)\\s*<"
let nameRegex = try? NSRegularExpression(pattern: namePattern)
let nameMatch = nameRegex?.firstMatch(in: htmlString, range: NSRange(htmlString.startIndex..., in: htmlString))
let name = nameMatch.flatMap { match in
Range(match.range(at: 1), in: htmlString)
}.map { range in
String(htmlString[range]).trimmingCharacters(in: .whitespacesAndNewlines)
}
// Extract email using regex
let emailPattern = "mailto:([^\"]+)\""
let emailRegex = try? NSRegularExpression(pattern: emailPattern)
let emailMatch = emailRegex?.firstMatch(in: htmlString, range: NSRange(htmlString.startIndex..., in: htmlString))
let email = emailMatch.flatMap { match in
Range(match.range(at: 1), in: htmlString)
}.map { range in
String(htmlString[range])
}
let pattern = "<div class=\"details-bloc\">\\s*(\\d{2}\\s+\\d{2}\\s+\\d{2}\\s+\\d{2}\\s+\\d{2})\\s*</div>"
var phoneNumber: String? = nil
// Look for the specific div and its content
if let range = htmlString.range(of: pattern, options: [.regularExpression, .caseInsensitive]) {
let match = String(htmlString[range])
let phonePattern = "\\d{2}\\s+\\d{2}\\s+\\d{2}\\s+\\d{2}\\s+\\d{2}"
if let phoneRange = match.range(of: phonePattern, options: .regularExpression) {
phoneNumber = String(match[phoneRange])
.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
return (name, email, phoneNumber)
}
} }

@ -31,6 +31,15 @@ struct EventLinksView: View {
var body: some View { var body: some View {
List { List {
if let tenupId = event.tenupId {
Section {
Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(tenupId)")!) {
Label("Voir sur Tenup", systemImage: "tennisball")
}
}
}
Section { Section {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
Picker(selection: $pageLink) { Picker(selection: $pageLink) {

@ -135,6 +135,14 @@ struct EventSettingsView: View {
}) })
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar { .toolbar {
if let tenupId = event.tenupId {
ToolbarItem(placement: .topBarTrailing) {
Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(tenupId)")!) {
Text("Tenup")
}
}
}
if focusedField != nil { if focusedField != nil {
ToolbarItem(placement: .keyboard) { ToolbarItem(placement: .keyboard) {
HStack { HStack {

@ -19,6 +19,7 @@ struct EventListView: View {
let sortAscending: Bool let sortAscending: Bool
@State var showUserSearch: Bool = false @State var showUserSearch: Bool = false
@State private var sectionImporting: Int? = nil
var lastDataSource: Date? { var lastDataSource: Date? {
guard let _lastDataSource = dataStore.appSettings.lastDataSource else { return nil } guard let _lastDataSource = dataStore.appSettings.lastDataSource else { return nil }
@ -49,9 +50,19 @@ struct EventListView: View {
if let pcTournaments = _tournaments as? [Tournament] { if let pcTournaments = _tournaments as? [Tournament] {
_menuOptions(pcTournaments) _menuOptions(pcTournaments)
} else if let federalTournaments = _tournaments as? [FederalTournament], navigation.agendaDestination == .tenup { } else if let federalTournaments = _tournaments as? [FederalTournament], navigation.agendaDestination == .tenup {
HStack {
FooterButtonView("Tout récupérer", role: .destructive) { FooterButtonView("Tout récupérer", role: .destructive) {
federalTournaments.forEach { federalTournament in Task {
_importFederalTournamentBatch(federalTournament: federalTournament) sectionImporting = sectionIndex
for federalTournament in federalTournaments {
await _importFederalTournamentBatch(federalTournament: federalTournament)
}
sectionImporting = nil
}
}
if sectionImporting == sectionIndex {
Spacer()
ProgressView()
} }
} }
} }
@ -293,6 +304,32 @@ struct EventListView: View {
} label: { } label: {
Text("Utiliser les réglages par défaut") Text("Utiliser les réglages par défaut")
} }
Button {
Task {
await pcTournaments.concurrentForEach { tournament in
if let tenupId = tournament.eventObject()?.tenupId {
let umpireData = try? await NetworkFederalService.shared.getUmpireData(idTournament: tenupId)
if let email = umpireData?.email {
tournament.umpireCustomMail = email
}
if let name = umpireData?.name {
tournament.umpireCustomContact = name.lowercased().capitalized
}
if let phone = umpireData?.phone {
tournament.umpireCustomPhone = phone
}
}
}
await MainActor.run {
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
}
}
} label: {
Text("Récuperer via Tenup")
}
} label: { } label: {
Text("Informations de contact Juge-Arbitre") Text("Informations de contact Juge-Arbitre")
} }
@ -406,21 +443,41 @@ struct EventListView: View {
return dataStore.events.first(where: { $0.tenupId == tournament.id.string }) return dataStore.events.first(where: { $0.tenupId == tournament.id.string })
} }
private func _importFederalTournamentBatch(federalTournament: FederalTournament) { private func _importFederalTournamentBatch(federalTournament: FederalTournament) async {
let templateTournament = Tournament.getTemplateTournament() let templateTournament = Tournament.getTemplateTournament()
let newTournaments = federalTournament.tournaments.compactMap { tournament in let newTournaments = await withTaskGroup(of: Tournament?.self) { group in
_create(federalTournament: federalTournament, existingTournament: _event(of: federalTournament)?.existingBuild(tournament), build: tournament, templateTournament: templateTournament) var tournaments: [Tournament] = []
for tournament in federalTournament.tournaments {
group.addTask {
await self._create(
federalTournament: federalTournament,
existingTournament: self._event(of: federalTournament)?.existingBuild(tournament),
build: tournament,
templateTournament: templateTournament
)
} }
}
for await tournament in group {
if let tournament = tournament {
tournaments.append(tournament)
}
}
return tournaments
}
dataStore.tournaments.addOrUpdate(contentOfs: newTournaments) dataStore.tournaments.addOrUpdate(contentOfs: newTournaments)
} }
private func _create(federalTournament: FederalTournament, existingTournament: Tournament?, build: any TournamentBuildHolder, templateTournament: Tournament?) -> Tournament? { private func _create(federalTournament: FederalTournament, existingTournament: Tournament?, build: any TournamentBuildHolder, templateTournament: Tournament?) async -> Tournament? {
if existingTournament == nil { guard existingTournament == nil else { return nil }
let event = federalTournament.getEvent() let event = federalTournament.getEvent()
let newTournament = Tournament.newEmptyInstance() let newTournament = Tournament.newEmptyInstance()
newTournament.event = event.id newTournament.event = event.id
//todo //todo
//newTournament.umpireMail()
//newTournament.jsonData = jsonData //newTournament.jsonData = jsonData
newTournament.tournamentLevel = build.level newTournament.tournamentLevel = build.level
newTournament.tournamentCategory = build.category newTournament.tournamentCategory = build.category
@ -428,10 +485,30 @@ struct EventListView: View {
newTournament.dayDuration = federalTournament.dayDuration newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9)
newTournament.initSettings(templateTournament: templateTournament) newTournament.initSettings(templateTournament: templateTournament)
return newTournament
} else { if federalTournament.umpireLabel().isEmpty == false {
return nil newTournament.umpireCustomContact = federalTournament.umpireLabel()
} }
if federalTournament.mailLabel().isEmpty == false {
newTournament.umpireCustomMail = federalTournament.mailLabel()
}
do {
let umpireData = try await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id)
if let email = umpireData.email {
newTournament.umpireCustomMail = email
}
if let name = umpireData.name {
newTournament.umpireCustomContact = name.lowercased().capitalized
}
if let phone = umpireData.phone {
newTournament.umpireCustomPhone = phone
}
} catch {
Logger.error(error)
}
return newTournament
} }
} }

@ -21,6 +21,7 @@ struct TournamentSubscriptionView: View {
@State private var sentError: ContactManagerError? = nil @State private var sentError: ContactManagerError? = nil
@State private var didSendMessage: Bool = false @State private var didSendMessage: Bool = false
@State private var didSaveInCalendar: Bool = false @State private var didSaveInCalendar: Bool = false
@State private var phoneNumber: String? = nil
init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) { init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) {
self.federalTournament = federalTournament self.federalTournament = federalTournament
@ -106,9 +107,15 @@ struct TournamentSubscriptionView: View {
LabeledContent("Mail") { LabeledContent("Mail") {
Text(federalTournament.mailLabel()) Text(federalTournament.mailLabel())
} }
LabeledContent("Téléphone") { LabeledContent("Téléphone Club") {
Text(federalTournament.phoneLabel()) Text(federalTournament.phoneLabel())
} }
if let phoneNumber {
LabeledContent("Téléphone JAP") {
Text(phoneNumber)
}
}
} header: { } header: {
Text("Informations") Text("Informations")
} }
@ -156,6 +163,9 @@ struct TournamentSubscriptionView: View {
CopyPasteButtonView(pasteValue: messageBody) CopyPasteButtonView(pasteValue: messageBody)
} }
} }
.task {
self.phoneNumber = try? await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id).phone
}
.toolbarBackground(.visible, for: .bottomBar) .toolbarBackground(.visible, for: .bottomBar)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.overlay(alignment: .bottom) { .overlay(alignment: .bottom) {
@ -176,7 +186,7 @@ struct TournamentSubscriptionView: View {
} }
} }
if let installation = federalTournament.installation, let telephone = installation.telephone { if let telephone = phoneNumber {
if telephone.isMobileNumber() { if telephone.isMobileNumber() {
Section { Section {
RowButtonView("S'inscrire par message", systemImage: "message") { RowButtonView("S'inscrire par message", systemImage: "message") {
@ -187,10 +197,24 @@ struct TournamentSubscriptionView: View {
let number = telephone.replacingOccurrences(of: " ", with: "") let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) { Link(destination: url) {
Label("Appeler", systemImage: "phone") Label("Appeler le JAP", systemImage: "phone")
} }
} }
} }
if let installation = federalTournament.installation, let telephone = installation.telephone {
Section {
RowButtonView("Contacter le club", systemImage: "house.and.flag") {
contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
}
}
let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler le club", systemImage: "phone")
}
}
}
} label: { } label: {
Text("Contact et inscription") Text("Contact et inscription")
} }

@ -499,7 +499,7 @@ struct UmpireView: View {
} header: { } header: {
Text("Juge-arbitre") Text("Juge-arbitre")
} footer: { } footer: {
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.") Text("Par défaut, les informations de Tenup sont récupérés, et si ce n'est pas le cas, 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.")
} }
} }

@ -358,7 +358,7 @@ struct TournamentGeneralSettingsView: View {
} header: { } header: {
Text("Juge-arbitre") Text("Juge-arbitre")
} footer: { } footer: {
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.") Text("Par défaut, les informations de Tenup sont récupérés, et si ce n'est pas le cas, 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.")
} }
} }
} }

@ -200,14 +200,37 @@ struct TournamentCellView: View {
newTournament.dayDuration = federalTournament.dayDuration newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9)
newTournament.initSettings(templateTournament: Tournament.getTemplateTournament()) newTournament.initSettings(templateTournament: Tournament.getTemplateTournament())
if federalTournament.umpireLabel().isEmpty == false {
newTournament.umpireCustomContact = federalTournament.umpireLabel()
}
if federalTournament.mailLabel().isEmpty == false {
newTournament.umpireCustomMail = federalTournament.mailLabel()
}
Task {
do { do {
try dataStore.tournaments.addOrUpdate(instance: newTournament) let umpireData = try await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id)
if let email = umpireData.email {
newTournament.umpireCustomMail = email
}
if let name = umpireData.name {
newTournament.umpireCustomContact = name.lowercased().capitalized
}
if let phone = umpireData.phone {
newTournament.umpireCustomPhone = phone
}
await MainActor.run {
dataStore.tournaments.addOrUpdate(instance: newTournament)
}
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} }
} }
} }
}
//#Preview { //#Preview {
// TournamentCellView(tournament: Tournament.fake()) // TournamentCellView(tournament: Tournament.fake())

Loading…
Cancel
Save