diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 77b74aa..2933cfc 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1935,7 +1935,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 99; + CURRENT_PROJECT_VERSION = 103; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1985,7 +1985,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 99; + CURRENT_PROJECT_VERSION = 103; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 13dc786..3f06644 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1368,7 +1368,12 @@ defer { if tournamentLevel == .unlisted, displayStyle == .title, let name { return name } - return [tournamentLevel.localizedLabel(displayStyle) + " " + tournamentCategory.localizedLabel(), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ") + let title: String = [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), federalTournamentAge.localizedLabel(displayStyle)].joined(separator: " ") + if displayStyle == .wide, let name { + return [title, name].joined(separator: " - ") + } else { + return title + } } func localizedTournamentType() -> String { diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index f3412a3..bec703e 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -101,13 +101,15 @@ class FileImportManager { let players: Set let weight: Int let tournamentCategory: TournamentCategory + let tournamentAgeCategory: FederalTournamentAge let previousTeam: TeamRegistration? var registrationDate: Date? = nil var name: String? = nil - init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) { + init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, tournamentAgeCategory: FederalTournamentAge, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) { self.players = Set(players) self.tournamentCategory = tournamentCategory + self.tournamentAgeCategory = tournamentAgeCategory self.name = name self.previousTeam = previousTeam if players.count < 2 { @@ -247,6 +249,11 @@ class FileImportManager { } } + let ageCategory = dataOne[1] + var tournamentAgeCategory: FederalTournamentAge { + FederalTournamentAge.allCases.first(where: { $0.importingRawValue.canonicalVersion == ageCategory.canonicalVersion }) ?? .senior + } + let resultOne = Array(dataOne.dropFirst(3).dropLast()) let resultTwo = Array(dataTwo.dropFirst(3).dropLast()) let sexUnknown: Bool = (resultOne.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true) || (resultTwo.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true) @@ -268,7 +275,7 @@ class FileImportManager { case .mix: return 1 } } - if tournamentCategory == tournament.tournamentCategory || checkingCategoryDisabled { + if (tournamentCategory == tournament.tournamentCategory && tournamentAgeCategory == tournament.federalTournamentAge) || checkingCategoryDisabled { let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) playerOne?.setComputedRank(in: tournament) let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown) @@ -276,7 +283,7 @@ class FileImportManager { let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { - let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) results.append(team) } } @@ -302,7 +309,14 @@ class FileImportManager { return .men } } - if tournamentCategory == tournament.tournamentCategory || checkingCategoryDisabled { + + + let ageCategory = data[1] + var tournamentAgeCategory: FederalTournamentAge { + FederalTournamentAge.allCases.first(where: { $0.importingRawValue.canonicalVersion == ageCategory.canonicalVersion }) ?? .senior + } + + if (tournamentCategory == tournament.tournamentCategory && tournamentAgeCategory == tournament.federalTournamentAge) || checkingCategoryDisabled { let result = Array(data.dropFirst(3).dropLast()) var sexPlayerOne : Int { @@ -330,7 +344,7 @@ class FileImportManager { let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { - let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) results.append(team) } } @@ -378,7 +392,7 @@ class FileImportManager { return nil } - let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament) + let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament) results.append(team) } } @@ -412,7 +426,7 @@ class FileImportManager { return player } - return TeamHolder(players: players, tournamentCategory: .men, previousTeam: nil, name: teamName, tournament: tournament) + return TeamHolder(players: players, tournamentCategory: .men, tournamentAgeCategory: .senior, previousTeam: nil, name: teamName, tournament: tournament) } return results } diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index d50d6a9..cb07550 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -170,6 +170,27 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { self.init(rawValue: value) } + var importingRawValue: String { + switch self { + case .unlisted: + return "Senior" + case .a11_12: + return "11/12 ans" + case .a13_14: + return "13/14 ans" + case .a15_16: + return "15/16 ans" + case .a17_18: + return "17/18 ans" + case .senior: + return "Senior" + case .a45: + return "45 ans" + case .a55: + return "55 ans" + } + } + static func mostRecent(inTournaments tournaments: [Tournament]) -> Self { return tournaments.first?.federalTournamentAge ?? .senior } @@ -777,11 +798,32 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { case .unlisted: return displayStyle == .title ? "Aucune" : "" case .men: - return displayStyle != .short ? "Hommes" : "H" + switch displayStyle { + case .title: + return "DH" + case .wide: + return "Hommes" + case .short: + return "H" + } case .women: - return displayStyle != .short ? "Dames" : "D" + switch displayStyle { + case .title: + return "DD" + case .wide: + return "Dames" + case .short: + return "D" + } case .mix: - return displayStyle != .short ? "Mixte" : "MX" + switch displayStyle { + case .title: + return "MX" + case .wide: + return "Mixte" + case .short: + return "MX" + } } } diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index 5b5ecfc..c588edd 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -128,7 +128,7 @@ class SearchViewModel: ObservableObject, Identifiable { } func words() -> [String] { - return searchText.canonicalVersion.trimmed.components(separatedBy: .whitespaces) + return searchText.canonicalVersionWithPunctuation.trimmed.components(separatedBy: .whitespaces) } func wordsPredicates() -> NSPredicate? { @@ -159,6 +159,10 @@ class SearchViewModel: ObservableObject, Identifiable { predicates.append(NSPredicate(format: "license contains[cd] %@", canonicalVersionWithoutPunctuation)) } predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation)) + let components = canonicalVersionWithoutPunctuation.split(separator: " ").sorted() + let pattern = components.joined(separator: ".*") + let predicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern) + predicates.append(predicate) } case .ligue: if canonicalVersionWithoutPunctuation.isEmpty { diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index 6d884fb..cc22c46 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -140,6 +140,7 @@ struct ClubSearchView: View { RowButtonView("D'accord") { locationManager.lastError = nil } + .padding(.horizontal) } } else if clubMarkers.isEmpty == false && searching == false && _filteredClubs().isEmpty { ContentUnavailableView.search(text: searchedCity) @@ -171,6 +172,7 @@ struct ClubSearchView: View { locationManager.requestLocation() } } + .padding(.horizontal) } if error != nil { @@ -182,11 +184,13 @@ struct ClubSearchView: View { RowButtonView("Chercher une ville ou un code postal") { searchPresented = true } - + .padding(.horizontal) + if searchAttempted { RowButtonView("Créer un club manuellement") { newClub = club ?? Club.newEmptyInstance() } + .padding(.horizontal) } } } diff --git a/PadelClub/Views/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index b5ea93e..041f4ca 100644 --- a/PadelClub/Views/Club/ClubsView.swift +++ b/PadelClub/Views/Club/ClubsView.swift @@ -159,9 +159,11 @@ struct ClubsView: View { RowButtonView("Créer un nouveau club", systemImage: "plus.circle.fill") { newClub = Club.newEmptyInstance() } + .padding(.horizontal) RowButtonView("Chercher un club", systemImage: "magnifyingglass.circle.fill") { presentClubSearchView = true } + .padding(.horizontal) } } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 42a545d..09ac9c4 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -109,15 +109,14 @@ struct ActivityView: View { RowButtonView("D'accord.") { self.error = nil } + .padding(.horizontal) } - .padding() } else if isGatheringFederalTournaments { ProgressView() } else { if tournaments.isEmpty && viewStyle == .list { if searchText.isEmpty == false { ContentUnavailableView.search(text: searchText) - .padding() } else if federalDataViewModel.areFiltersEnabled() { ContentUnavailableView { Text("Aucun résultat") @@ -127,11 +126,10 @@ struct ActivityView: View { RowButtonView("supprimer le filtre") { federalDataViewModel.removeFilters() } + .padding(.horizontal) } - .padding() } else { _dataEmptyView() - .padding() } } } @@ -320,10 +318,13 @@ struct ActivityView: View { RowButtonView("Créer un nouvel événement") { newTournament = Tournament.newEmptyInstance() } + .padding(.horizontal) RowButtonView("Importer via Tenup") { navigation.agendaDestination = .tenup } + .padding(.horizontal) SupportButtonView(contentIsUnavailable: true) + .padding(.horizontal) } } @@ -345,6 +346,7 @@ struct ActivityView: View { RowButtonView("Choisir mes clubs") { presentClubSearchView = true } + .padding() } } else { ContentUnavailableView { @@ -356,6 +358,7 @@ struct ActivityView: View { NetworkFederalService.shared.formId = "" _gatherFederalTournaments() } + .padding() } } } diff --git a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift index 9ade115..31b14ea 100644 --- a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift +++ b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift @@ -114,6 +114,7 @@ struct CourtAvailabilitySettingsView: View { endDate = tournament.startDate.addingTimeInterval(5400) showingPopover = true } + .padding(.horizontal) } } } diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index a4780a4..ae4a4e5 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -79,6 +79,7 @@ struct PlanningView: View { RowButtonView("Horaire intelligent") { selectedScheduleDestination = nil } + .padding(.horizontal) } } } diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index eb59336..8229b25 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -436,17 +436,15 @@ struct MySearchView: View { Text(searchViewModel.contentUnavailableMessage) } actions: { - Button { + RowButtonView("Lancer une nouvelle recherche") { searchViewModel.debouncableText = "" - } label: { - Text("lancer une nouvelle recherche") } + .padding() if let contentUnavailableAction { - Button { + RowButtonView("Créer \(searchViewModel.searchText)") { contentUnavailableAction(searchViewModel) - } label: { - Text("créer \(searchViewModel.searchText)") } + .padding() } } } diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 1917e61..7529e94 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -90,7 +90,7 @@ struct FileImportView: View { } private func filteredTeams(tournament: Tournament) -> [FileImportManager.TeamHolder] { - return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight) + return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight) } private func _deleteTeams() async { @@ -149,7 +149,7 @@ struct FileImportView: View { } } - if let event = tournament.eventObject(), event.tenupId != nil, event.tournaments.count > 1, fileProvider == .frenchFederation { + if let event = tournament.eventObject(), event.tournaments.count > 1, fileProvider == .frenchFederation { Section { RowButtonView("Importer pour tous les tournois") { multiImport = true @@ -230,7 +230,7 @@ struct FileImportView: View { if filteredTeams.isEmpty && teams.isEmpty == false && multiImport == false { @Bindable var tournament = tournament Section { - Text("Aucune équipe \(tournament.tournamentCategory.importingRawValue) détectée mais \(teams.count) équipes sont dans le fichier") + Text("Aucune équipe \(tournament.tournamentCategory.importingRawValue) \(tournament.federalAgeCategory.importingRawValue) détectée mais \(teams.count) équipes sont dans le fichier") Picker(selection: $tournament.tournamentCategory) { ForEach([TournamentCategory.men, TournamentCategory.women, TournamentCategory.mix]) { category in Text(category.importingRawValue).tag(category) @@ -250,6 +250,26 @@ struct FileImportView: View { } } } + Picker(selection: $tournament.federalTournamentAge) { + ForEach(FederalTournamentAge.allCases) { ageCategory in + Text(ageCategory.importingRawValue).tag(ageCategory) + } + } label: { + Text("Modifier la catégorie d'âge") + } + .onChange(of: tournament.federalTournamentAge) { + _save() + Task { + if let fileContent { + do { + try await _startImport(fileContent: fileContent) + } catch { + errorMessage = error.localizedDescription + } + } + } + } + } } else if teams.isEmpty && didImport == true { Section { @@ -285,7 +305,7 @@ struct FileImportView: View { LabeledContent { Text(_filteredTeams.count.formatted()) } label: { - Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) détectée\(_filteredTeams.count.pluralSuffix)") + Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) \(tournament.federalTournamentAge.importingRawValue) détectée\(_filteredTeams.count.pluralSuffix)") } } footer: { if previousTeams.isEmpty == false { @@ -444,6 +464,16 @@ struct FileImportView: View { } } + + struct CombinedCategory: Identifiable, Hashable { + let tournamentCategory: TournamentCategory + let federalTournamentAge: FederalTournamentAge + + var id: String { + return tournamentCategory.importingRawValue + federalTournamentAge.importingRawValue + } + } + private func _startImport(fileContent: String) async throws { await MainActor.run { errorMessage = nil @@ -454,15 +484,16 @@ struct FileImportView: View { } let event: Event? = tournament.eventObject() - if let event, event.tenupId != nil { - var categoriesDone: [TournamentCategory] = [] + if let event, event.tournaments.count > 1 { + var categoriesDone: [CombinedCategory] = [] for someTournament in event.tournaments { - if categoriesDone.contains(someTournament.tournamentCategory) == false { + let combinedCategory = CombinedCategory(tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge) + if categoriesDone.contains(combinedCategory) == false { let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: someTournament, fileProvider: fileProvider, checkingCategoryDisabled: false) self.teams += _teams - categoriesDone.append(someTournament.tournamentCategory) + categoriesDone.append(combinedCategory) } else { - errorMessage = "Attention, l'événement possède plusieurs tournois d'une même catégorie (homme, femme, mixte), Padel Club ne peut savoir quelle équipe appartient à quel tournoi." + errorMessage = "Attention, l'événement possède plusieurs tournois d'une même catégorie / âge, Padel Club ne peut savoir quelle équipe appartient à quel tournoi." } } } else { diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index a7b2dfb..afd2fd3 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -215,6 +215,11 @@ struct AddTeamView: View { orPredicates = nameComponents.map { NSPredicate(format: "firstName contains[cd] %@ OR lastName contains[cd] %@", $0,$0) } } + let components = text.split(separator: " ").sorted() + let pattern = components.joined(separator: ".*") + let canonicalFullNamePredicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern) + orPredicates.append(canonicalFullNamePredicate) + let matches = text.licencesFound() let licensesPredicates = matches.map { NSPredicate(format: "license contains[cd] %@", $0) } orPredicates = orPredicates + licensesPredicates diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index fd0852d..1382e5a 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -224,10 +224,12 @@ struct InscriptionManagerView: View { RowButtonView("Ajouter une équipe") { presentAddTeamView = true } - + .padding(.horizontal) + RowButtonView("Importer un fichier") { presentImportView = true } + .padding(.horizontal) } .padding() }