diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 6c7e3f8..140f9a4 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -108,6 +108,7 @@ FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; }; FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; }; FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; + FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; @@ -453,6 +454,8 @@ FF1F4B7F2BFA0105000B4573 /* tournament-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "tournament-template.html"; sourceTree = ""; }; FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = ""; }; FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanningByCourtView.swift; sourceTree = ""; }; + FF2B51602C7E302C00FFF126 /* local.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = local.sqlite; sourceTree = ""; }; + FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_1_1.xcdatamodel; sourceTree = ""; }; FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = ""; }; FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; @@ -681,6 +684,7 @@ C40CD2F22C412681000DBD9A /* AppDelegate.swift */, C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */, FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, + FF2B515F2C7E300500FFF126 /* SeedData */, C4A47D722B72881500ADC637 /* Views */, FF3F74FD2B91A087004CFE0E /* ViewModel */, C4A47D5F2B6D3B2D00ADC637 /* Data */, @@ -688,7 +692,6 @@ FFF8ACD72B923F26008466FA /* Extensions */, C425D4042B6D249E002A7B48 /* Assets.xcassets */, FFF024192BF48AEE001F14B4 /* Localization */, - FFA4DA502C75E815002DAA31 /* Files */, FF0EC54D2BB195CA0056B6D1 /* CSV */, FF1F4B802BFA0105000B4573 /* HTML Templates */, C425D4062B6D249E002A7B48 /* Preview Content */, @@ -969,6 +972,14 @@ path = "HTML Templates"; sourceTree = ""; }; + FF2B515F2C7E300500FFF126 /* SeedData */ = { + isa = PBXGroup; + children = ( + FF2B51602C7E302C00FFF126 /* local.sqlite */, + ); + path = SeedData; + sourceTree = ""; + }; FF39719B2B8DE04B004C4E75 /* Navigation */ = { isa = PBXGroup; children = ( @@ -1234,13 +1245,6 @@ path = Components; sourceTree = ""; }; - FFA4DA502C75E815002DAA31 /* Files */ = { - isa = PBXGroup; - children = ( - ); - path = Files; - sourceTree = ""; - }; FFBF41802BF73EA2001B24CB /* Event */ = { isa = PBXGroup; children = ( @@ -1500,6 +1504,7 @@ C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */, FF44421C2BE39FA2008BBF0B /* Launch Screen.storyboard in Resources */, FF1F4B832BFA02A4000B4573 /* tournament-template.html in Resources */, + FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */, FF1F4B842BFA02A4000B4573 /* groupstagescore-template.html in Resources */, FF1F4B852BFA02A4000B4573 /* player-template.html in Resources */, FF1F4B862BFA02A4000B4573 /* groupstagerow-template.html in Resources */, @@ -1955,7 +1960,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -2005,7 +2010,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -2197,9 +2202,10 @@ FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */, FF3795612B9396D0004EA093 /* Model.xcdatamodel */, ); - currentVersion = FF3795612B9396D0004EA093 /* Model.xcdatamodel */; + currentVersion = FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */; path = PadelClubApp.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 10cd021..6dc4216 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -13,7 +13,13 @@ extension ImportedPlayer: PlayerHolder { return getRank()?.femaleInMaleAssimilation } - var computedAge: Int? { nil } + var computedAge: Int? { + let year = Calendar.current.getSportAge() + if let yearOfBirth = birthYear?.toInt() { + return year - yearOfBirth + } + return nil + } var tournamentPlayed: Int? { Int(tournamentCount) @@ -96,6 +102,18 @@ extension ImportedPlayer: PlayerHolder { } return 0 } + + func getBirthYear() -> Int? { + if let birthYear { + return Int(birthYear) + } else { + return nil + } + } + + func getProgression() -> Int { + return Int(progression) + } } fileprivate extension Int { diff --git a/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/.xccurrentversion b/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..9858596 --- /dev/null +++ b/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Model_1_1.xcdatamodel + + diff --git a/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/Model_1_1.xcdatamodel/contents b/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/Model_1_1.xcdatamodel/contents new file mode 100644 index 0000000..8d2cb42 --- /dev/null +++ b/PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/Model_1_1.xcdatamodel/contents @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PadelClub/Data/Coredata/Persistence.swift b/PadelClub/Data/Coredata/Persistence.swift index 69ba981..3cfd69b 100644 --- a/PadelClub/Data/Coredata/Persistence.swift +++ b/PadelClub/Data/Coredata/Persistence.swift @@ -10,6 +10,12 @@ import CoreData class PersistenceController: NSObject { static let shared = PersistenceController() + private static var prepopulatedSeed: URL? { + let url = Bundle.main.url(forResource: "local", withExtension: "sqlite") + print("prepopulatedSeed", url) + return url + } + private static var _model: NSManagedObjectModel? private static func model(name: String) throws -> NSManagedObjectModel { if _model == nil { @@ -39,6 +45,12 @@ class PersistenceController: NSObject { let localStoreFolderURL = storeFolderURL.appendingPathComponent("Local") let fileManager = FileManager.default + + var firstLaunch = false + if fileManager.fileExists(atPath: localStoreFolderURL.path()) == false { + firstLaunch = true + } + for folderURL in [localStoreFolderURL] where !fileManager.fileExists(atPath: folderURL.path) { do { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) @@ -47,6 +59,17 @@ class PersistenceController: NSObject { } } + if firstLaunch, let seedURL = PersistenceController.prepopulatedSeed { + let folderPath = seedURL.path() + let localStoreFileURL = localStoreFolderURL.appendingPathComponent("local.sqlite") + do { + try fileManager.copyItem(at: seedURL, to: localStoreFileURL) + } catch { + print("Error info: \(error)") + } + } + + let container = NSPersistentContainer(name: "PadelClubApp", managedObjectModel: try! Self.model(name: "PadelClubApp")) guard let localStoreDescription = container.persistentStoreDescriptions.first!.copy() as? NSPersistentStoreDescription else { @@ -175,6 +198,9 @@ class PersistenceController: NSObject { importedPlayer.clubCode = data.clubCode.replaceCharactersFromSet(characterSet: .whitespaces) importedPlayer.male = data.isMale importedPlayer.importDate = importingDate + importedPlayer.progression = Int64(data.progression) + importedPlayer.bestRank = data.bestRank?.formattedAsRawString() + importedPlayer.birthYear = data.birthYear?.formattedAsRawString() } // 5 diff --git a/PadelClub/Data/Federal/FederalPlayer.swift b/PadelClub/Data/Federal/FederalPlayer.swift index 4742b83..a8b2780 100644 --- a/PadelClub/Data/Federal/FederalPlayer.swift +++ b/PadelClub/Data/Federal/FederalPlayer.swift @@ -20,6 +20,9 @@ class FederalPlayer: Decodable { var clubCode: String var club: String var isMale: Bool + var birthYear: Int? + var progression: Int + var bestRank: Int? // MARK: - Nationnalite @@ -61,13 +64,13 @@ class FederalPlayer: Decodable { let nationnalite = try container.decode(Nationnalite.self, forKey: .nationnalite) country = nationnalite.code - //meilleurClassement = try container.decode(Int.self, forKey: .meilleurClassement) - //anneeNaissance = try container.decode(Int.self, forKey: .anneeNaissance) + 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) + progression = (try? container.decodeIfPresent(Int.self, forKey: .progression)) ?? 0 let pointsAsInt = try? container.decodeIfPresent(Int.self, forKey: .points) if let pointsAsInt { points = Double(pointsAsInt) @@ -84,7 +87,7 @@ class FederalPlayer: Decodable { let pointsString = points != nil ? String(Int(points!)) : "" let tournamentCountString = tournamentCount != nil ? String(tournamentCount!) : "" let strippedLicense = license.strippedLicense ?? "" - let line = ";\(rank);\(lastName);\(firstName);\(country);\(strippedLicense);\(pointsString);\(assimilation);\(tournamentCountString);\(ligue);\(formatNumbers(clubCode));\(club);" + let line = ";\(rank);\(lastName);\(firstName);\(country);\(strippedLicense);\(pointsString);\(assimilation);\(tournamentCountString);\(ligue);\(formatNumbers(clubCode));\(club);\(progression.formattedAsRawString());\(bestRank?.formattedAsRawString() ?? "");\(birthYear?.formattedAsRawString() ?? "");" return line } @@ -167,6 +170,9 @@ class FederalPlayer: Decodable { ligue = result[8] clubCode = result[9] club = result[10] + progression = result[safe: 11]?.toInt() ?? 0 + bestRank = result[safe: 12]?.toInt() + birthYear = result[safe: 13]?.toInt() } static func anonymousCount(mostRecentDateAvailable: Date?) async -> Int? { @@ -191,6 +197,8 @@ class FederalPlayer: Decodable { } lastPlayerFetch.predicate = predicate let count = try? context.count(for: lastPlayerFetch) + + print("count", count) do { if let lr = try context.fetch(lastPlayerFetch).first?.rank { let fetch = ImportedPlayer.fetchRequest() diff --git a/PadelClub/Data/Federal/PlayerHolder.swift b/PadelClub/Data/Federal/PlayerHolder.swift index d0a6801..07c6860 100644 --- a/PadelClub/Data/Federal/PlayerHolder.swift +++ b/PadelClub/Data/Federal/PlayerHolder.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI protocol PlayerHolder { @@ -24,6 +25,8 @@ protocol PlayerHolder { var computedAge: Int? { get } func getAssimilatedAsMaleRank() -> Int? func isNotFromCurrentDate() -> Bool + func getBirthYear() -> Int? + func getProgression() -> Int } extension PlayerHolder { @@ -38,4 +41,16 @@ extension PlayerHolder { func isAnonymous() -> Bool { getFirstName().isEmpty && getLastName().isEmpty } + + func getProgressionColor(progression: Int) -> Color { + switch progression { + case _ where progression > 0: + return Color.green + case _ where progression < 0: + return Color.logoRed + default: + return Color.primary + } + + } } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index aa70f03..b53db2f 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -124,7 +124,9 @@ final class PlayerRegistration: ModelObject, Storable { if let birthdate { let components = birthdate.components(separatedBy: "/") if components.count == 3 { - if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) { + if let age = components.last, let ageInt = Int(age) { + let year = Calendar.current.getSportAge() + if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier if ageInt < 23 { return year - 2000 - ageInt @@ -571,4 +573,12 @@ extension PlayerRegistration: PlayerHolder { var male: Bool { isMalePlayer() } + + func getBirthYear() -> Int? { + nil + } + + func getProgression() -> Int { + 0 + } } diff --git a/PadelClub/Extensions/Calendar+Extensions.swift b/PadelClub/Extensions/Calendar+Extensions.swift index bcf6600..3e04ff3 100644 --- a/PadelClub/Extensions/Calendar+Extensions.swift +++ b/PadelClub/Extensions/Calendar+Extensions.swift @@ -23,4 +23,29 @@ extension Calendar { return numberOfDaysBetween(date1, and: date2) == 0 } + func getSportAge() -> Int { + let currentDate = Date() + + // Get the current year + let currentYear = component(.year, from: currentDate) + + // Define the date components for 1st September and 31st December of the current year + var septemberFirstComponents = DateComponents(year: currentYear, month: 9, day: 1) + var decemberThirtyFirstComponents = DateComponents(year: currentYear, month: 12, day: 31) + + // Get the actual dates for 1st September and 31st December + let septemberFirst = date(from: septemberFirstComponents)! + let decemberThirtyFirst = date(from: decemberThirtyFirstComponents)! + + // Determine the sport year + let sportYear: Int + if currentDate >= septemberFirst && currentDate <= decemberThirtyFirst { + // If after 1st September and before 31st December, use current year + 1 + sportYear = currentYear + 1 + } else { + // Otherwise, use the current year + sportYear = currentYear + } + return sportYear + } } diff --git a/PadelClub/Extensions/FixedWidthInteger+Extensions.swift b/PadelClub/Extensions/FixedWidthInteger+Extensions.swift index eccc6bd..0309c74 100644 --- a/PadelClub/Extensions/FixedWidthInteger+Extensions.swift +++ b/PadelClub/Extensions/FixedWidthInteger+Extensions.swift @@ -22,4 +22,8 @@ public extension FixedWidthInteger { var pluralSuffix: String { return self > 1 ? "s" : "" } + + func formattedAsRawString() -> String { + String(self) + } } diff --git a/PadelClub/SeedData/local.sqlite b/PadelClub/SeedData/local.sqlite new file mode 100644 index 0000000..4936fb7 Binary files /dev/null and b/PadelClub/SeedData/local.sqlite differ diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 394d72a..8c65472 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -170,6 +170,28 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { self.init(rawValue: value) } + func computedBirthYear() -> (Int?, Int?) { + let year = Calendar.current.getSportAge() + switch self { + case .unlisted: + return (nil, nil) + case .a11_12: + return (year - 12, year - 11) + case .a13_14: + return (year - 14, year - 13) + case .a15_16: + return (year - 16, year - 15) + case .a17_18: + return (year - 18, year - 17) + case .senior: + return (nil, year - 19) + case .a45: + return (nil, year - 45) + case .a55: + return (nil, year - 55) + } + } + var importingRawValue: String { switch self { case .unlisted: @@ -185,9 +207,9 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { case .senior: return "Senior" case .a45: - return "45 ans" + return "+45 ans" case .a55: - return "55 ans" + return "+55 ans" } } @@ -243,21 +265,14 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { case .senior: return "Senior" case .a45: - return "45 ans" + return "+45 ans" case .a55: - return "55 ans" + return "+55 ans" } } var tournamentDescriptionLabel: String { - switch self { - case .senior: - return "" - case .a45, .a55: - return "+" + localizedLabel() - default: - return localizedLabel() - } + return localizedLabel() } } diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index a3ec84f..43aa224 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -35,6 +35,7 @@ class SearchViewModel: ObservableObject, Identifiable { @Published var selectedPlayers: Set = Set() @Published var filterSelectionEnabled: Bool = false @Published var isPresented: Bool = false + @Published var selectedAgeCategory: FederalTournamentAge = .unlisted var mostRecentDate: Date? = nil @@ -90,11 +91,15 @@ class SearchViewModel: ObservableObject, Identifiable { } func showIndex() -> Bool { - if (dataSet == .national || dataSet == .ligue) { return false } - if filterOption == .all { return false } + if (dataSet == .national || dataSet == .ligue) { return isFiltering() } + if filterOption == .all { return isFiltering() } return true } + func isFiltering() -> Bool { + searchText.isEmpty == false || tokens.isEmpty == false || hideAssimilation || selectedAgeCategory != .unlisted + } + func prompt(forDataSet: DataSet) -> String { switch forDataSet { case .national: @@ -195,6 +200,13 @@ class SearchViewModel: ObservableObject, Identifiable { } else { predicates.append(NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!)) } + case .age: + 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())) + } + } if predicates.isEmpty { @@ -222,6 +234,17 @@ class SearchViewModel: ObservableObject, Identifiable { predicates.append(NSPredicate(format: "assimilation == %@", "Non")) } + if selectedAgeCategory != .unlisted { + let computedBirthYear = selectedAgeCategory.computedBirthYear() + if let left = computedBirthYear.0 { + predicates.append(NSPredicate(format: "birthYear >= %@", left.formattedAsRawString())) + } + if let right = computedBirthYear.1 { + predicates.append(NSPredicate(format: "birthYear <= %@", right.formattedAsRawString())) + } + + } + switch dataSet { case .national: break @@ -267,7 +290,8 @@ enum SearchToken: String, CaseIterable, Identifiable { case rankMoreThan = "rang >" case rankLessThan = "rang <" case rankBetween = "rang <>" - + case age = "âge sportif" + var id: String { rawValue } @@ -284,6 +308,8 @@ enum SearchToken: String, CaseIterable, Identifiable { 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" + case .age: + return "Taper une année de naissance" } } @@ -297,6 +323,8 @@ enum SearchToken: String, CaseIterable, Identifiable { return "Chercher un rang" case .rankBetween: return "Chercher une intervalle de classement" + case .age: + return "Chercher une année de naissance" } } @@ -312,6 +340,8 @@ enum SearchToken: String, CaseIterable, Identifiable { return "Rang inférieur ou égale à" case .rankBetween: return "Rang entre deux valeurs" + case .age: + return "Année de naissance" } } @@ -327,6 +357,8 @@ enum SearchToken: String, CaseIterable, Identifiable { return "Rang ≤" case .rankBetween: return "Rang ≥,≤" + case .age: + return "Né(e) en" } } @@ -342,6 +374,8 @@ enum SearchToken: String, CaseIterable, Identifiable { return "figure.racquetball" case .rankBetween: return "figure.racquetball" + case .age: + return "figure.racquetball" } } @@ -357,6 +391,8 @@ enum SearchToken: String, CaseIterable, Identifiable { return "figure.racquetball" case .rankBetween: return "figure.racquetball" + case .age: + return "figure.racquetball" } } } @@ -387,13 +423,13 @@ enum DataSet: Int, Identifiable { var tokens: [SearchToken] { switch self { case .national: - return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween] + return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween, .age] case .ligue: - return [.club, .rankMoreThan, .rankLessThan, .rankBetween] + return [.club, .rankMoreThan, .rankLessThan, .rankBetween, .age] case .club: - return [.rankMoreThan, .rankLessThan, .rankBetween] + return [.rankMoreThan, .rankLessThan, .rankBetween, .age] case .favoritePlayers, .favoriteClubs: - return [.rankMoreThan, .rankLessThan, .rankBetween] + return [.rankMoreThan, .rankLessThan, .rankBetween, .age] } } } @@ -403,6 +439,7 @@ enum SortOption: Int, CaseIterable, Identifiable { case rank case tournamentCount case points + case progression var id: Int { self.rawValue } func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { @@ -415,6 +452,8 @@ enum SortOption: Int, CaseIterable, Identifiable { return "Tournoi" case .points: return "Points" + case .progression: + return "Progression" } } @@ -432,6 +471,8 @@ enum SortOption: Int, CaseIterable, Identifiable { 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)] + case .progression: + return [SortDescriptor(\ImportedPlayer.progression, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)] } } } diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index dda58d7..047c121 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -186,7 +186,7 @@ struct MainView: View { importObserver.checkingFiles = false if lastDataSource == nil || (dataStore.monthData.first(where: { $0.monthKey == "07-2024" }) == nil) { - await _downloadPreviousDate() +// await _downloadPreviousDate() await _importMandatoryData() } @@ -226,7 +226,10 @@ struct MainView: View { let mandatoryKey = "07-2024" if dataStore.monthData.first(where: { $0.monthKey == mandatoryKey }) == nil, let importingDate = URL.importDateFormatter.date(from: mandatoryKey) { print("importing mandatory july data") - await _startImporting(importingDate: importingDate) + dataStore.appSettings.lastDataSource = mandatoryKey + dataStore.appSettingsStorage.write() + await SourceFileManager.shared.getAllFiles(initialDate: "07-2024") + await _calculateMonthData(dataSource: mandatoryKey) } } diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index 5de7806..95311c8 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -187,17 +187,6 @@ struct PadelClubView: View { Spacer() Text(monthData.total().formatted() + " joueurs") } - } footer: { - HStack { - Spacer() - FooterButtonView("recalculer") { - Task { - if let monthKeyDate = URL.importDateFormatter.date(from: monthData.monthKey) { - await MonthData.calculateCurrentUnrankedValues(fromDate: monthKeyDate) - } - } - } - } } } } diff --git a/PadelClub/Views/Shared/ImportedPlayerView.swift b/PadelClub/Views/Shared/ImportedPlayerView.swift index 0f65159..70b41e6 100644 --- a/PadelClub/Views/Shared/ImportedPlayerView.swift +++ b/PadelClub/Views/Shared/ImportedPlayerView.swift @@ -11,7 +11,8 @@ struct ImportedPlayerView: View { let player: PlayerHolder var index: Int? = nil var showFemaleInMaleAssimilation: Bool = false - + @State var showProgression: Bool = false + var body: some View { VStack(alignment: .leading) { HStack { @@ -52,6 +53,15 @@ struct ImportedPlayerView: View { .font(.caption) } } + + if player.getProgression() != 0 { + HStack(alignment: .top, spacing: 2) { + Text("(") + Text(player.getProgression().formatted(.number.sign(strategy: .always()))) + .foregroundStyle(player.getProgressionColor(progression: player.getProgression())) + Text(")") + }.font(.title3) + } if let pts = player.getPoints(), pts > 0 { HStack(alignment: .lastTextBaseline, spacing: 0) { @@ -68,7 +78,7 @@ struct ImportedPlayerView: View { } } - if let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank(), showFemaleInMaleAssimilation { + if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { HStack(alignment: .top, spacing: 2) { Text("(") Text(assimilatedAsMaleRank.formatted()) @@ -80,7 +90,7 @@ struct ImportedPlayerView: View { Text(")").font(.title3) } } - + HStack { Text(player.formattedLicense()) if let computedAge = player.computedAge { diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index 8229b25..45025ec 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -19,6 +19,8 @@ struct SelectablePlayerListView: View { let contentUnavailableAction: ContentUnavailableAction? @EnvironmentObject var dataStore: DataStore + @Environment(ImportObserver.self) private var importObserver: ImportObserver + @StateObject private var searchViewModel: SearchViewModel @Environment(\.dismiss) var dismiss @@ -26,8 +28,6 @@ struct SelectablePlayerListView: View { dataStore.appSettings.lastDataSource } - @AppStorage("importingFiles") var importingFiles: Bool = false - @State private var searchText: String = "" var mostRecentDate: Date? { guard let lastDataSource else { return nil } @@ -61,15 +61,76 @@ struct SelectablePlayerListView: View { var body: some View { VStack(spacing: 0) { - if importingFiles == false { + if importObserver.isImportingFile() == false { if searchViewModel.filterSelectionEnabled == false { - Picker(selection: $searchViewModel.filterOption) { - ForEach(PlayerFilterOption.allCases, id: \.self) { scope in - Text(scope.icon().capitalized) + VStack { + HStack { + Picker(selection: $searchViewModel.filterOption) { + ForEach(PlayerFilterOption.allCases, id: \.self) { scope in + Text(scope.icon().capitalized) + } + } label: { + } + .pickerStyle(.segmented) + Menu { + Section { + ForEach(SortOption.allCases) { option in + Toggle(isOn: .init(get: { + return searchViewModel.sortOption == option + }, set: { value in + if searchViewModel.sortOption == option { + searchViewModel.ascending.toggle() + } + searchViewModel.sortOption = option + })) { + Label(option.localizedLabel(), systemImage: searchViewModel.sortOption == option ? (searchViewModel.ascending ? "chevron.up" : "chevron.down") : "") + } + } + } header: { + Text("Trier par") + } + Divider() + Section { + Picker(selection: $searchViewModel.selectedAgeCategory) { + ForEach(FederalTournamentAge.allCases) { ageCategory in + Text(ageCategory.localizedLabel(.title)).tag(ageCategory) + } + } label: { + Text("Catégorie d'âge") + } + + } header: { + Text("Catégorie d'âge") + } + Divider() + Section { + Toggle(isOn: .init(get: { + return searchViewModel.hideAssimilation == false + }, set: { value in + searchViewModel.hideAssimilation.toggle() + })) { + Text("Afficher") + } + Toggle(isOn: .init(get: { + return searchViewModel.hideAssimilation == true + }, set: { value in + searchViewModel.hideAssimilation.toggle() + })) { + Text("Masquer") + } + } header: { + Text("Assimilés") + } + } label: { + VStack(alignment: .trailing) { + Label(searchViewModel.sortOption.localizedLabel(), systemImage: searchViewModel.ascending ? "chevron.up" : "chevron.down") + if searchViewModel.selectedAgeCategory != .unlisted { + Text(searchViewModel.selectedAgeCategory.localizedLabel()).font(.caption) + } + } + } } - } label: { } - .pickerStyle(.segmented) .padding(.bottom) .padding(.horizontal) .background(Material.thick) @@ -119,9 +180,9 @@ struct SelectablePlayerListView: View { } } } - .id(importingFiles) + .id(importObserver.currentImportDate) .overlay { - if let importedFile = SourceFileManager.shared.mostRecentDateAvailable, importingFiles { + if let importedFile = SourceFileManager.shared.mostRecentDateAvailable, importObserver.isImportingFile() { ContentUnavailableView("Importation en cours", systemImage: "square.and.arrow.down", description: Text("Padel Club récupère les données de \(importedFile.monthYearFormatted)")) } @@ -168,6 +229,11 @@ struct SelectablePlayerListView: View { searchViewModel.filterSelectionEnabled = false } } + .onChange(of: searchViewModel.tokens) { + if searchViewModel.tokens.first == .age { + searchViewModel.selectedAgeCategory = .unlisted + } + } .toolbar { if searchViewModel.allowMultipleSelection { ToolbarItemGroup(placement: .topBarLeading) { @@ -341,8 +407,9 @@ struct MySearchView: View { .id(UUID()) } else { Section { - ForEach(players) { player in - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ForEach(players.indices, id: \.self) { index in + let player = players[index] + ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) } } header: { if players.isEmpty == false { @@ -365,7 +432,7 @@ struct MySearchView: View { .frame(maxWidth: .infinity) .buttonStyle(.plain) } else { - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) } } } header: { @@ -383,45 +450,6 @@ struct MySearchView: View { HStack { Text("\(players.count.formatted()) \( searchViewModel.filterOption.localizedPlayerLabel)\( players.count.pluralSuffix)") Spacer() - Menu { - Section { - ForEach(SortOption.allCases) { option in - Toggle(isOn: .init(get: { - return searchViewModel.sortOption == option - }, set: { value in - if searchViewModel.sortOption == option { - searchViewModel.ascending.toggle() - } - searchViewModel.sortOption = option - })) { - Label(option.localizedLabel(), systemImage: searchViewModel.sortOption == option ? (searchViewModel.ascending ? "chevron.up" : "chevron.down") : "") - } - } - } header: { - Text("Trier par") - } - Divider() - Section { - Toggle(isOn: .init(get: { - return searchViewModel.hideAssimilation == false - }, set: { value in - searchViewModel.hideAssimilation.toggle() - })) { - Text("Afficher") - } - Toggle(isOn: .init(get: { - return searchViewModel.hideAssimilation == true - }, set: { value in - searchViewModel.hideAssimilation.toggle() - })) { - Text("Masquer") - } - } header: { - Text("Assimilés") - } - } label: { - Label(searchViewModel.sortOption.localizedLabel(), systemImage: searchViewModel.ascending ? "chevron.up" : "chevron.down") - } } }