fix issue with search

sync_v2
Raz 8 months ago
parent b27cdfa1e6
commit f83cb5f251
  1. 393
      PadelClub/ViewModel/SearchViewModel.swift
  2. 4
      PadelClub/Views/Tournament/Screen/AddTeamView.swift

@ -14,7 +14,7 @@ class DebouncableViewModel: ObservableObject {
class SearchViewModel: ObservableObject, Identifiable {
let id: UUID = UUID()
var allowSelection : Int = 0
var allowSelection: Int = 0
var codeClub: String? = nil
var clubName: String? = nil
var ligueName: String? = nil
@ -67,7 +67,9 @@ class SearchViewModel: ObservableObject, Identifiable {
var contentUnavailableMessage: String {
var message = ["Vérifiez l'ortographe ou lancez une nouvelle recherche."]
if tokens.isEmpty {
message.append("Il est possible que cette personne n'est joué aucun tournoi depuis les 12 derniers mois, dans ce cas, Padel Club ne pourra pas le trouver.")
message.append(
"Il est possible que cette personne n'est joué aucun tournoi depuis les 12 derniers mois, dans ce cas, Padel Club ne pourra pas le trouver."
)
}
return message.joined(separator: "\n")
}
@ -79,13 +81,17 @@ class SearchViewModel: ObservableObject, Identifiable {
func getCodeClub() -> String? {
if let codeClub { return codeClub }
if let userCodeClub = DataStore.shared.user.currentPlayerData()?.clubCode { return userCodeClub }
if let userCodeClub = DataStore.shared.user.currentPlayerData()?.clubCode {
return userCodeClub
}
return nil
}
func getLigueName() -> String? {
if let ligueName { return ligueName }
if let userLigueName = DataStore.shared.user.currentPlayerData()?.ligueName { return userLigueName }
if let userLigueName = DataStore.shared.user.currentPlayerData()?.ligueName {
return userLigueName
}
return nil
}
@ -102,24 +108,30 @@ class SearchViewModel: ObservableObject, Identifiable {
return true
}
return dataSet == .national && searchText.isEmpty == false && (tokens.isEmpty == true && hideAssimilation == false && selectedAgeCategory == .unlisted)
return dataSet == .national && searchText.isEmpty == false
&& (tokens.isEmpty == true && hideAssimilation == false
&& selectedAgeCategory == .unlisted)
}
func showIndex() -> Bool {
if dataSet == .national {
if searchText.isEmpty == false && (tokens.isEmpty == true && hideAssimilation == false && selectedAgeCategory == .unlisted) {
if searchText.isEmpty == false
&& (tokens.isEmpty == true && hideAssimilation == false
&& selectedAgeCategory == .unlisted)
{
return false
} else {
return isFiltering()
}
}
if (dataSet == .ligue) { return isFiltering() }
if dataSet == .ligue { return isFiltering() }
if filterOption == .all { return isFiltering() }
return true
}
func isFiltering() -> Bool {
searchText.isEmpty == false || tokens.isEmpty == false || hideAssimilation || selectedAgeCategory != .unlisted
searchText.isEmpty == false || tokens.isEmpty == false || hideAssimilation
|| selectedAgeCategory != .unlisted
}
func prompt(forDataSet: DataSet) -> String {
@ -155,41 +167,117 @@ class SearchViewModel: ObservableObject, Identifiable {
}
func words() -> [String] {
return searchText.canonicalVersionWithPunctuation.trimmed.components(separatedBy: .whitespaces)
return searchText.canonicalVersionWithPunctuation.trimmed.components(
separatedBy: .whitespaces)
}
func wordsPredicates() -> NSPredicate? {
let words = words().filter({ $0.isEmpty == false })
// Handle special case of hyphenated words
let hyphenatedWords = searchText.components(separatedBy: .whitespaces)
.filter { $0.contains("-") }
var predicates: [NSPredicate] = []
// Add predicates for hyphenated words
for word in hyphenatedWords {
predicates.append(NSPredicate(format: "lastName CONTAINS[cd] %@", word))
let parts = word.components(separatedBy: "-")
for part in parts where part.count > 1 {
predicates.append(NSPredicate(format: "lastName CONTAINS[cd] %@", part))
}
}
// Regular words processing
switch words.count {
case 2:
let predicates = [
NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[0], words[1]),
NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[1], words[0]),
]
return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
predicates.append(contentsOf: [
NSPredicate(
format:
"canonicalLastName CONTAINS[cd] %@ AND canonicalFirstName CONTAINS[cd] %@",
words[0], words[1]),
NSPredicate(
format:
"canonicalLastName CONTAINS[cd] %@ AND canonicalFirstName CONTAINS[cd] %@",
words[1], words[0]),
// For multi-word first names, try the two words as a first name
NSPredicate(
format: "canonicalFirstName CONTAINS[cd] %@", words.joined(separator: " ")),
])
case 3:
// Handle potential cases like "Jean Christophe CROS"
predicates.append(contentsOf: [
// First two words as first name, last as last name
NSPredicate(
format:
"canonicalFirstName CONTAINS[cd] %@ AND canonicalLastName CONTAINS[cd] %@",
words[0] + " " + words[1], words[2]),
// First as first name, last two as last name
NSPredicate(
format:
"canonicalFirstName CONTAINS[cd] %@ AND canonicalLastName CONTAINS[cd] %@",
words[0], words[1] + " " + words[2]),
// Last as first name, first two as last name
NSPredicate(
format:
"canonicalFirstName CONTAINS[cd] %@ AND canonicalLastName CONTAINS[cd] %@",
words[2], words[0] + " " + words[1]),
])
default:
return nil
if words.count > 0 {
// For single word or many words, try matching against full name
predicates.append(
NSPredicate(
format: "canonicalFullName CONTAINS[cd] %@",
words.joined(separator: " ")))
}
}
return predicates.isEmpty
? nil : NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
}
func searchTextPredicate() -> NSPredicate? {
var predicates : [NSPredicate] = []
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces)
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion.components(separatedBy: allowedCharacterSet.inverted).joined().trimmed
var predicates: [NSPredicate] = []
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces).union(
CharacterSet(charactersIn: "-"))
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion
.components(separatedBy: allowedCharacterSet.inverted)
.joined()
.trimmed
if canonicalVersionWithoutPunctuation.isEmpty == false {
let wordsPredicates = wordsPredicates()
if let wordsPredicates {
predicates.append(wordsPredicates)
} else {
predicates.append(NSPredicate(format: "license contains[cd] %@", canonicalVersionWithoutPunctuation))
predicates.append(
NSPredicate(
format: "license contains[cd] %@", canonicalVersionWithoutPunctuation))
}
predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation))
// Add match for full name
predicates.append(
NSPredicate(
format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation)
)
// Add pattern match for more flexible matching
let components = canonicalVersionWithoutPunctuation.split(separator: " ")
let pattern = components.joined(separator: ".*")
let predicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern)
predicates.append(predicate)
predicates.append(NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern))
// Look for exact matches on first or last name
let words = canonicalVersionWithoutPunctuation.components(separatedBy: .whitespaces)
for word in words where word.count > 2 {
predicates.append(
NSPredicate(
format: "firstName CONTAINS[cd] %@ OR lastName CONTAINS[cd] %@", word, word)
)
}
}
if predicates.isEmpty {
return nil
}
@ -197,60 +285,83 @@ class SearchViewModel: ObservableObject, Identifiable {
}
func orPredicate() -> NSPredicate? {
var predicates : [NSPredicate] = []
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces)
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion.components(separatedBy: allowedCharacterSet.inverted).joined().trimmed
var predicates: [NSPredicate] = []
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces).union(
CharacterSet(charactersIn: "-"))
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion
.components(separatedBy: allowedCharacterSet.inverted)
.joined()
.trimmed
let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation.trimmed
if tokens.isEmpty {
if shouldIncludeSearchTextPredicate(), canonicalVersionWithoutPunctuation.isEmpty == false {
if shouldIncludeSearchTextPredicate(),
canonicalVersionWithoutPunctuation.isEmpty == false
{
if let searchTextPredicate = searchTextPredicate() {
predicates.append(searchTextPredicate)
}
}
}
// Process tokens
for token in tokens {
switch token {
case .ligue:
if canonicalVersionWithoutPunctuation.isEmpty {
predicates.append(NSPredicate(format: "ligueName == nil"))
} else {
predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation))
predicates.append(
NSPredicate(
format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation)
)
}
case .club:
if canonicalVersionWithoutPunctuation.isEmpty {
predicates.append(NSPredicate(format: "clubName == nil"))
} else {
predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation))
predicates.append(
NSPredicate(
format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation))
}
case .rankMoreThan:
if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
if canonicalVersionWithoutPunctuation.isEmpty
|| Int(canonicalVersionWithoutPunctuation) == 0
{
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation))
predicates.append(
NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation))
}
case .rankLessThan:
if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
if canonicalVersionWithoutPunctuation.isEmpty
|| Int(canonicalVersionWithoutPunctuation) == 0
{
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation))
predicates.append(
NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation))
}
case .rankBetween:
let values = canonicalVersionWithPunctuation.components(separatedBy: ",")
if canonicalVersionWithPunctuation.isEmpty || values.count != 2 {
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!))
predicates.append(
NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!))
}
case .age:
if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
if canonicalVersionWithoutPunctuation.isEmpty
|| Int(canonicalVersionWithoutPunctuation) == 0
{
predicates.append(NSPredicate(format: "birthYear == 0"))
} else if let birthYear = Int(canonicalVersionWithoutPunctuation) {
predicates.append(NSPredicate(format: "birthYear == %@", birthYear.formattedAsRawString()))
predicates.append(
NSPredicate(format: "birthYear == %@", birthYear.formattedAsRawString()))
}
}
}
if predicates.isEmpty {
return nil
}
@ -258,14 +369,10 @@ class SearchViewModel: ObservableObject, Identifiable {
}
func predicate() -> NSPredicate? {
var predicates : [NSPredicate?] = [
var predicates: [NSPredicate?] = [
orPredicate(),
filterOption == .male ?
NSPredicate(format: "male == YES") :
nil,
filterOption == .female ?
NSPredicate(format: "male == NO") :
nil,
filterOption == .male ? NSPredicate(format: "male == YES") : nil,
filterOption == .female ? NSPredicate(format: "male == NO") : nil,
]
if let mostRecentDate {
@ -279,10 +386,12 @@ class SearchViewModel: ObservableObject, Identifiable {
if selectedAgeCategory != .unlisted {
let computedBirthYear = selectedAgeCategory.computedBirthYear()
if let left = computedBirthYear.0 {
predicates.append(NSPredicate(format: "birthYear >= %@", left.formattedAsRawString()))
predicates.append(
NSPredicate(format: "birthYear >= %@", left.formattedAsRawString()))
}
if let right = computedBirthYear.1 {
predicates.append(NSPredicate(format: "birthYear <= %@", right.formattedAsRawString()))
predicates.append(
NSPredicate(format: "birthYear <= %@", right.formattedAsRawString()))
}
}
@ -316,7 +425,6 @@ class SearchViewModel: ObservableObject, Identifiable {
return NSCompoundPredicate(andPredicateWithSubpredicates: predicates.compactMap({ $0 }))
}
func sortDescriptors() -> [SortDescriptor<ImportedPlayer>] {
sortOption.sortDescriptors(ascending, dataSet: dataSet)
}
@ -347,18 +455,21 @@ class SearchViewModel: ObservableObject, Identifiable {
}
// Create the NSPredicate for searching in the `lastName` field
let predicate = NSPredicate(format: "lastName CONTAINS[cd] %@ OR lastName CONTAINS[cd] %@", lastName1, lastName2)
let predicate = NSPredicate(
format: "lastName CONTAINS[cd] %@ OR lastName CONTAINS[cd] %@", lastName1, lastName2)
// Output the result
//print("Generated Predicate: \(predicate)")
return predicate
}
static func pastePredicate(pasteField: String, mostRecentDate: Date?, filterOption: PlayerFilterOption) -> NSPredicate? {
static func pastePredicate(
pasteField: String, mostRecentDate: Date?, filterOption: PlayerFilterOption
) -> NSPredicate? {
var andPredicates = [NSPredicate]()
var orPredicates = [NSPredicate]()
// Check for license numbers
let matches = pasteField.licencesFound()
let licensesPredicates = matches.map { NSPredicate(format: "license contains[cd] %@", $0) }
orPredicates = licensesPredicates
@ -367,59 +478,110 @@ class SearchViewModel: ObservableObject, Identifiable {
return NSCompoundPredicate(orPredicateWithSubpredicates: orPredicates)
}
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces)
// Remove all characters that are not in the allowedCharacterSet
var text = pasteField.canonicalVersion.components(separatedBy: allowedCharacterSet.inverted).joined().trimmedMultiline
// Define the regex pattern to match digits
let digitPattern = /\b\w*\d\w*\b/
// Replace all occurrences of the pattern (digits) with an empty string
text = text.replacing(digitPattern, with: "").trimmingCharacters(in: .whitespacesAndNewlines)
let textStrings: [String] = text.components(separatedBy: .whitespacesAndNewlines)
let nonEmptyStrings: [String] = textStrings.compactMap { $0.isEmpty ? nil : $0 }
let nameComponents = nonEmptyStrings.filter({ $0 != "de" && $0 != "la" && $0 != "le" && $0.count > 1 })
//self.wordsCount = nameComponents.count
// Add gender filter if specified
if filterOption == .female {
andPredicates.append(NSPredicate(format: "male == NO"))
} else if filterOption == .male {
andPredicates.append(NSPredicate(format: "male == YES"))
}
// Add date filter if specified
if let mostRecentDate {
andPredicates.append(NSPredicate(format: "importDate == %@", mostRecentDate as CVarArg))
}
// Check for slashes (representing alternatives)
if let slashPredicate = getSpecialSlashPredicate(inputString: pasteField) {
orPredicates.append(slashPredicate)
}
print("nameComponents", nameComponents.count)
// Prepare text for processing - preserve hyphens but remove digits
var text =
pasteField
.replacingOccurrences(of: "/", with: " ") // Replace slashes with spaces
.trimmingCharacters(in: .whitespacesAndNewlines)
if nameComponents.count < 50 {
if nameComponents.count > 1 {
orPredicates.append(contentsOf: nameComponents.pairs().map {
return NSPredicate(format: "(firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@) OR (firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@)", $0, $1, $1, $0) })
} else {
orPredicates.append(contentsOf: nameComponents.map { NSPredicate(format: "firstName contains[cd] %@ OR lastName contains[cd] %@", $0,$0) })
// Remove digits
let digitPattern = /\b\w*\d\w*\b/
text = text.replacing(digitPattern, with: "").trimmingCharacters(
in: .whitespacesAndNewlines)
// Split text by whitespace to get potential name components
let textComponents = text.components(separatedBy: .whitespacesAndNewlines)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter {
!$0.isEmpty && $0.count > 1 && !["de", "la", "le", "du"].contains($0.lowercased())
}
}
let components = text.split(separator: " ")
let pattern = components.joined(separator: ".*")
print(text, pattern)
let canonicalFullNamePredicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern)
orPredicates.append(canonicalFullNamePredicate)
if textComponents.count < 50 {
// Handle exact fullname match
let fullName = textComponents.joined(separator: " ")
if !fullName.isEmpty {
orPredicates.append(
NSPredicate(format: "canonicalFullName CONTAINS[cd] %@", fullName))
}
var predicate = NSCompoundPredicate(andPredicateWithSubpredicates: andPredicates)
// Handle hyphenated last names
let hyphenatedComponents = textComponents.filter { $0.contains("-") }
for component in hyphenatedComponents {
orPredicates.append(NSPredicate(format: "lastName CONTAINS[cd] %@", component))
// Also search for each part of the hyphenated name
let parts = component.components(separatedBy: "-")
for part in parts {
if part.count > 1 {
orPredicates.append(NSPredicate(format: "lastName CONTAINS[cd] %@", part))
}
}
}
if orPredicates.isEmpty == false {
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, NSCompoundPredicate(orPredicateWithSubpredicates: orPredicates)])
// Try different combinations for first/last name
if textComponents.count > 1 {
// Try each pair of components as first+last and last+first
for i in 0..<textComponents.count {
for j in 0..<textComponents.count where i != j {
orPredicates.append(
NSPredicate(
format:
"(firstName CONTAINS[cd] %@ AND lastName CONTAINS[cd] %@) OR (firstName CONTAINS[cd] %@ AND lastName CONTAINS[cd] %@)",
textComponents[i], textComponents[j], textComponents[j],
textComponents[i]
))
// Also try beginswith for more precise matches
orPredicates.append(
NSPredicate(
format:
"(firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@) OR (firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@)",
textComponents[i], textComponents[j], textComponents[j],
textComponents[i]
))
}
}
} else if textComponents.count == 1 {
// If only one component, search in both first and last name
orPredicates.append(
NSPredicate(
format: "firstName CONTAINS[cd] %@ OR lastName CONTAINS[cd] %@",
textComponents[0], textComponents[0]))
}
// Add pattern match for canonical full name
let pattern = textComponents.joined(separator: ".*")
orPredicates.append(NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern))
}
// Construct final predicate
var predicate = NSCompoundPredicate(andPredicateWithSubpredicates: andPredicates)
if !orPredicates.isEmpty {
let orCompoundPredicate = NSCompoundPredicate(
orPredicateWithSubpredicates: orPredicates)
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
predicate, orCompoundPredicate,
])
}
print(predicate)
return predicate
}
@ -440,15 +602,20 @@ enum SearchToken: String, CaseIterable, Identifiable {
var message: String {
switch self {
case .club:
return "Taper le nom d'un club pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
return
"Taper le nom d'un club pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
case .ligue:
return "Taper le nom d'une ligue pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
return
"Taper le nom d'une ligue pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
case .rankMoreThan:
return "Taper un nombre pour chercher les joueurs ayant un classement supérieur ou égale."
return
"Taper un nombre pour chercher les joueurs ayant un classement supérieur ou égale."
case .rankLessThan:
return "Taper un nombre pour chercher les joueurs ayant un classement inférieur ou égale."
return
"Taper un nombre pour chercher les joueurs ayant un classement inférieur ou égale."
case .rankBetween:
return "Taper deux nombres séparés par une virgule pour chercher les joueurs dans cette intervalle de classement"
return
"Taper deux nombres séparés par une virgule pour chercher les joueurs dans cette intervalle de classement"
case .age:
return "Taper une année de naissance"
}
@ -545,7 +712,7 @@ enum DataSet: Int, Identifiable {
case favoriteClubs
case favoritePlayers
static let allCases : [DataSet] = [.national, .ligue, .club, .favoriteClubs]
static let allCases: [DataSet] = [.national, .ligue, .club, .favoriteClubs]
var id: Int { rawValue }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@ -562,7 +729,7 @@ enum DataSet: Int, Identifiable {
}
var tokens: [SearchToken] {
var _tokens : [SearchToken] = []
var _tokens: [SearchToken] = []
switch self {
case .national:
_tokens = [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween]
@ -605,19 +772,41 @@ enum SortOption: Int, CaseIterable, Identifiable {
func sortDescriptors(_ ascending: Bool, dataSet: DataSet) -> [SortDescriptor<ImportedPlayer>] {
switch self {
case .name:
return [SortDescriptor(\ImportedPlayer.lastName, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation)]
return [
SortDescriptor(\ImportedPlayer.lastName, order: ascending ? .forward : .reverse),
SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation),
]
case .rank:
if (dataSet == .national || dataSet == .ligue) {
return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse)]
if dataSet == .national || dataSet == .ligue {
return [
SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse)
]
} else {
return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
return [
SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse),
SortDescriptor(\ImportedPlayer.assimilation),
SortDescriptor(\ImportedPlayer.lastName),
]
}
case .tournamentCount:
return [SortDescriptor(\ImportedPlayer.tournamentCount, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
return [
SortDescriptor(
\ImportedPlayer.tournamentCount, order: ascending ? .forward : .reverse),
SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation),
SortDescriptor(\ImportedPlayer.lastName),
]
case .points:
return [SortDescriptor(\ImportedPlayer.points, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
return [
SortDescriptor(\ImportedPlayer.points, order: ascending ? .forward : .reverse),
SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation),
SortDescriptor(\ImportedPlayer.lastName),
]
case .progression:
return [SortDescriptor(\ImportedPlayer.progression, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
return [
SortDescriptor(\ImportedPlayer.progression, order: ascending ? .forward : .reverse),
SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation),
SortDescriptor(\ImportedPlayer.lastName),
]
}
}
}
@ -631,12 +820,12 @@ enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable {
func icon() -> String {
switch self {
case .all:
return "Tous"
case .male:
return "Homme"
case .female:
return "Femme"
case .all:
return "Tous"
case .male:
return "Homme"
case .female:
return "Femme"
}
}

@ -214,7 +214,9 @@ struct AddTeamView: View {
ToolbarItem(placement: .bottomBar) {
PasteButton(payloadType: String.self) { strings in
let first = strings.first ?? ""
handlePasteString(first)
DispatchQueue.main.async {
self.handlePasteString(first)
}
}
.disabled(_limitPlayerCount())
.foregroundStyle(.master)

Loading…
Cancel
Save