add prepoluted seed

add progression and birthYear data from tenup
release
Raz 1 year ago
parent 7e5e295c8f
commit 6bf9bdf634
  1. 28
      PadelClub.xcodeproj/project.pbxproj
  2. 20
      PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift
  3. 8
      PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/.xccurrentversion
  4. 30
      PadelClub/Data/Coredata/PadelClubApp.xcdatamodeld/Model_1_1.xcdatamodel/contents
  5. 26
      PadelClub/Data/Coredata/Persistence.swift
  6. 16
      PadelClub/Data/Federal/FederalPlayer.swift
  7. 15
      PadelClub/Data/Federal/PlayerHolder.swift
  8. 12
      PadelClub/Data/PlayerRegistration.swift
  9. 25
      PadelClub/Extensions/Calendar+Extensions.swift
  10. 4
      PadelClub/Extensions/FixedWidthInteger+Extensions.swift
  11. BIN
      PadelClub/SeedData/local.sqlite
  12. 39
      PadelClub/Utils/PadelRule.swift
  13. 55
      PadelClub/ViewModel/SearchViewModel.swift
  14. 7
      PadelClub/Views/Navigation/MainView.swift
  15. 11
      PadelClub/Views/Navigation/Umpire/PadelClubView.swift
  16. 16
      PadelClub/Views/Shared/ImportedPlayerView.swift
  17. 132
      PadelClub/Views/Shared/SelectablePlayerListView.swift

@ -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 = "<group>"; };
FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = "<group>"; };
FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanningByCourtView.swift; sourceTree = "<group>"; };
FF2B51602C7E302C00FFF126 /* local.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = local.sqlite; sourceTree = "<group>"; };
FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_1_1.xcdatamodel; sourceTree = "<group>"; };
FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = "<group>"; };
FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = "<group>"; };
FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
@ -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 = "<group>";
};
FF2B515F2C7E300500FFF126 /* SeedData */ = {
isa = PBXGroup;
children = (
FF2B51602C7E302C00FFF126 /* local.sqlite */,
);
path = SeedData;
sourceTree = "<group>";
};
FF39719B2B8DE04B004C4E75 /* Navigation */ = {
isa = PBXGroup;
children = (
@ -1234,13 +1245,6 @@
path = Components;
sourceTree = "<group>";
};
FFA4DA502C75E815002DAA31 /* Files */ = {
isa = PBXGroup;
children = (
);
path = Files;
sourceTree = "<group>";
};
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 = "<group>";
versionGroupType = wrapper.xcdatamodel;

@ -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 {

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Model_1_1.xcdatamodel</string>
</dict>
</plist>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E214" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="ImportedPlayer" representedClassName=".ImportedPlayer" syncable="YES" codeGenerationType="class">
<attribute name="assimilation" attributeType="String"/>
<attribute name="bestRank" optional="YES" attributeType="String"/>
<attribute name="birthYear" optional="YES" attributeType="String"/>
<attribute name="canonicalFirstName" optional="YES" attributeType="String" derived="YES" derivationExpression="canonical:(firstName)"/>
<attribute name="canonicalFullName" optional="YES" attributeType="String" derived="YES" derivationExpression="canonical:(fullName)"/>
<attribute name="canonicalLastName" optional="YES" attributeType="String" derived="YES" derivationExpression="canonical:(lastName)"/>
<attribute name="clubCode" attributeType="String"/>
<attribute name="clubName" attributeType="String"/>
<attribute name="country" attributeType="String"/>
<attribute name="firstName" attributeType="String"/>
<attribute name="fullName" attributeType="String"/>
<attribute name="importDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastName" attributeType="String"/>
<attribute name="license" attributeType="String"/>
<attribute name="ligueName" attributeType="String"/>
<attribute name="male" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="points" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="progression" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rank" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tournamentCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="license"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

@ -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

@ -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()

@ -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
}
}
}

@ -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
}
}

@ -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
}
}

@ -22,4 +22,8 @@ public extension FixedWidthInteger {
var pluralSuffix: String {
return self > 1 ? "s" : ""
}
func formattedAsRawString() -> String {
String(self)
}
}

Binary file not shown.

@ -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()
}
}

@ -35,6 +35,7 @@ class SearchViewModel: ObservableObject, Identifiable {
@Published var selectedPlayers: Set<ImportedPlayer> = 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)]
}
}
}

@ -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)
}
}

@ -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)
}
}
}
}
}
}
}

@ -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 {

@ -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")
}
}
}

Loading…
Cancel
Save