Merge branch 'main'

#Conflicts:
#	PadelClub/Views/Player/PlayerDetailView.swift
online_reg
Raz 10 months ago
commit 344a1f8747
  1. 28
      PadelClub.xcodeproj/project.pbxproj
  2. 25
      PadelClub/Data/PlayerRegistration.swift
  3. 2
      PadelClub/Data/TeamRegistration.swift
  4. 21
      PadelClub/Data/Tournament.swift
  5. 2
      PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift
  6. 28
      PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift
  7. 35
      PadelClub/Views/Player/PlayerDetailView.swift
  8. 12
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  9. 15
      PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift
  10. 13
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift

@ -3286,7 +3286,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3299,7 +3299,10 @@
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi";
INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "En utilisant votre position, Padel Club peut trouver plus rapidement les clubs et les tournois autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
@ -3310,7 +3313,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.41;
MARKETING_VERSION = 1.0.42;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3331,7 +3334,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -3343,7 +3346,10 @@
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi";
INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "En utilisant votre position, Padel Club peut trouver plus rapidement les clubs et les tournois autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
@ -3354,7 +3360,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.41;
MARKETING_VERSION = 1.0.42;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3460,7 +3466,10 @@
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi";
INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
@ -3504,7 +3513,10 @@
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi";
INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";

@ -257,8 +257,15 @@ final class PlayerRegistration: ModelObject, Storable {
}
}
@MainActor
func updateRank(from sources: [CSVParser], lastRank: Int) async throws {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
if let dataFound = try await history(from: sources) {
rank = dataFound.rankValue?.toInt()
points = dataFound.points
@ -269,6 +276,14 @@ final class PlayerRegistration: ModelObject, Storable {
}
func history(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
guard let license = licenceId?.strippedLicense else {
return try await historyFromName(from: sources)
}
@ -294,6 +309,14 @@ final class PlayerRegistration: ModelObject, Storable {
}
func historyFromName(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return await withTaskGroup(of: Line?.self) { group in
for source in sources.filter({ $0.maleData == isMalePlayer() }) {
group.addTask { [lastName, firstName] in

@ -183,7 +183,7 @@ final class TeamRegistration: ModelObject, Storable {
}
func getPhoneNumbers() -> [String] {
return players().compactMap { $0.phoneNumber }.filter({ $0.isMobileNumber() })
return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false })
}
func getMail() -> [String] {

@ -1183,7 +1183,7 @@ defer {
}
}
func registrationIssues() async -> Int {
func registrationIssues() -> Int {
let players : [PlayerRegistration] = unsortedPlayers()
let selectedTeams : [TeamRegistration] = selectedSortedTeams()
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
@ -1496,6 +1496,15 @@ defer {
}
func updateRank(to newDate: Date?) async throws {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
guard let newDate else { return }
rankSourceDate = newDate
@ -1519,8 +1528,8 @@ defer {
let lastRankWoman = currentMonthData()?.femaleUnrankedValue
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate }
let sources = dataURLs.map { CSVParser(url: $0) }
try await unsortedPlayers().concurrentForEach { player in
let players = unsortedPlayers()
try await players.concurrentForEach { player in
try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0)
}
}
@ -2747,7 +2756,11 @@ extension Tournament {
func deadline(for type: TournamentDeadlineType) -> Date? {
guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil }
if let date = Calendar.current.date(byAdding: .day, value: type.daysOffset, to: startDate) {
var daysOffset = type.daysOffset
if tournamentLevel == .p500 {
daysOffset += 7
}
if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) {
let startOfDay = Calendar.current.startOfDay(for: date)
return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay)
}

@ -45,7 +45,7 @@ struct PlayersWithoutContactView: View {
LabeledContent {
Text(withoutPhones.count.formatted())
} label: {
Text("Joueurs sans téléphone portable")
Text("Joueurs sans téléphone portable français")
}
}
} header: {

@ -18,7 +18,7 @@ struct TournamentLookUpView: View {
@State private var searchField: String = ""
@State var page: Int = 0
@State var total: Int = 0
@State private var showingSettingsAlert = false
@State private var searching: Bool = false
@State private var requestedToGetAllPages: Bool = false
@State private var revealSearchParameters: Bool = true
@ -57,6 +57,16 @@ struct TournamentLookUpView: View {
} message: {
Text("Aucune ville n'a été indiqué, il est préférable de se localiser ou d'indiquer une ville pour réduire le nombre de résultat.")
}
.alert(isPresented: $showingSettingsAlert) {
Alert(
title: Text("Réglages"),
message: Text("Pour trouver les clubs autour de vous, vous devez l'autorisation à Padel Club de récupérer votre position."),
primaryButton: .default(Text("Ouvrir les réglages"), action: {
_openSettings()
}),
secondaryButton: .cancel()
)
}
.alert("Attention", isPresented: $presentAlert, actions: {
Button {
presentAlert = false
@ -305,10 +315,16 @@ struct TournamentLookUpView: View {
}
if locationManager.requestStarted {
ProgressView()
} else {
} else if locationManager.manager.authorizationStatus != .restricted {
LocationButton {
if locationManager.manager.authorizationStatus == .notDetermined {
locationManager.manager.requestWhenInUseAuthorization()
} else if locationManager.manager.authorizationStatus == .denied {
showingSettingsAlert = true
} else {
locationManager.requestLocation()
}
}
.symbolVariant(.fill)
.foregroundColor (Color.white)
.cornerRadius (20)
@ -485,4 +501,12 @@ struct TournamentLookUpView: View {
return "Distance"
}
}
private func _openSettings() {
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsURL)
}
}

@ -158,10 +158,6 @@ struct PlayerDetailView: View {
Menu {
CopyPasteButtonView(pasteValue: player.phoneNumber)
PasteButtonView(text: $phoneNumber)
.onChange(of: phoneNumber) {
player.phoneNumber = phoneNumber.prefixTrimmed(50)
_save()
}
} label: {
Text("Téléphone")
}
@ -183,10 +179,6 @@ struct PlayerDetailView: View {
Menu {
CopyPasteButtonView(pasteValue: player.email)
PasteButtonView(text: $email)
.onChange(of: email) {
player.email = email.prefixTrimmed(50)
_save()
}
} label: {
Text("Email")
}
@ -230,13 +222,24 @@ struct PlayerDetailView: View {
}
}
.onChange(of: [player.hasArrived, player.captain, player.coach]) {
.onChange(of: player.hasArrived) {
_save()
}
.onChange(of: player.sex) {
_save()
}
.navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: {
.headerProminence(.increased)
.navigationTitle("Édition")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: player.pasteData()) {
Label("Partager", systemImage: "square.and.arrow.up")
}
}
if focusedField != nil {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
@ -244,14 +247,9 @@ struct PlayerDetailView: View {
}
}
}
})
.headerProminence(.increased)
.navigationTitle("Édition")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
if focusedField == ._rank || focusedField == ._computedRank {
ToolbarItem(placement: .keyboard) {
if focusedField == ._rank || focusedField == ._computedRank || focusedField == ._phoneNumber {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Valider") {
if focusedField == ._rank {
player.setComputedRank(in: tournament)
@ -260,6 +258,9 @@ struct PlayerDetailView: View {
} else if focusedField == ._computedRank {
player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory)
_save()
} else if focusedField == ._phoneNumber {
player.phoneNumber = phoneNumber.prefixTrimmed(50)
_save()
}
focusedField = nil
}

@ -9,7 +9,7 @@ import SwiftUI
struct InscriptionInfoView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament
let tournament: Tournament
@State private var players : [PlayerRegistration] = []
@State private var selectedTeams : [TeamRegistration] = []
@ -244,16 +244,17 @@ struct InscriptionInfoView: View {
Text("importé du fichier beach-padel sans licence valide ou créé sans licence")
}
}
.task {
await _getIssues()
.onAppear {
DispatchQueue.main.async {
_getIssues()
}
}
.navigationTitle("Synthèse")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
private func _getIssues() async {
Task {
private func _getIssues() {
players = tournament.unsortedPlayers()
selectedTeams = tournament.selectedSortedTeams()
callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) }
@ -268,7 +269,6 @@ struct InscriptionInfoView: View {
entriesFromBeachPadel = tournament.unsortedTeams().filter({ $0.isImported() })
playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
}
}
}
//#Preview {

@ -43,30 +43,29 @@ struct UpdateSourceRankDateView: View {
Task {
do {
try await tournament.updateRank(to: currentRankSourceDate)
try await MainActor.run {
let unsortedPlayers = tournament.unsortedPlayers()
tournament.unsortedPlayers().forEach { player in
player.setComputedRank(in: tournament)
}
try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: tournament.unsortedPlayers())
try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers)
tournament.unsortedTeams().forEach { team in
let unsortedTeams = tournament.unsortedTeams()
unsortedTeams.forEach { team in
team.setWeight(from: team.players(), inTournamentCategory: tournament.tournamentCategory)
if forceRefreshLockWeight {
team.lockedWeight = team.weight
}
}
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
try dataStore.tournaments.addOrUpdate(instance: tournament)
updatingRank = false
confirmUpdateRank = false
}
} catch {
Logger.error(error)
}
updatingRank = false
confirmUpdateRank = false
}
}.disabled(updatingRank)

@ -192,8 +192,8 @@ struct InscriptionManagerView: View {
self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
}
self.registrationIssues = nil
Task {
self.registrationIssues = await tournament.registrationIssues()
DispatchQueue.main.async {
self.registrationIssues = tournament.registrationIssues()
}
}
@ -789,14 +789,7 @@ struct InscriptionManagerView: View {
if tournament.isAnimation() == false {
NavigationLink {
InscriptionInfoView()
.environment(tournament)
.onDisappear {
self.registrationIssues = nil
Task {
self.registrationIssues = await tournament.registrationIssues()
}
}
InscriptionInfoView(tournament: tournament)
} label: {
LabeledContent {
if let registrationIssues {

Loading…
Cancel
Save