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. 53
      PadelClub/ViewModel/SearchViewModel.swift
  14. 7
      PadelClub/Views/Navigation/MainView.swift
  15. 11
      PadelClub/Views/Navigation/Umpire/PadelClubView.swift
  16. 12
      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 */; }; 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 */; }; FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; };
FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; 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 */; }; FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; };
FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; };
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
@ -681,6 +684,7 @@
C40CD2F22C412681000DBD9A /* AppDelegate.swift */, C40CD2F22C412681000DBD9A /* AppDelegate.swift */,
C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */, C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */,
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */, FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */,
FF2B515F2C7E300500FFF126 /* SeedData */,
C4A47D722B72881500ADC637 /* Views */, C4A47D722B72881500ADC637 /* Views */,
FF3F74FD2B91A087004CFE0E /* ViewModel */, FF3F74FD2B91A087004CFE0E /* ViewModel */,
C4A47D5F2B6D3B2D00ADC637 /* Data */, C4A47D5F2B6D3B2D00ADC637 /* Data */,
@ -688,7 +692,6 @@
FFF8ACD72B923F26008466FA /* Extensions */, FFF8ACD72B923F26008466FA /* Extensions */,
C425D4042B6D249E002A7B48 /* Assets.xcassets */, C425D4042B6D249E002A7B48 /* Assets.xcassets */,
FFF024192BF48AEE001F14B4 /* Localization */, FFF024192BF48AEE001F14B4 /* Localization */,
FFA4DA502C75E815002DAA31 /* Files */,
FF0EC54D2BB195CA0056B6D1 /* CSV */, FF0EC54D2BB195CA0056B6D1 /* CSV */,
FF1F4B802BFA0105000B4573 /* HTML Templates */, FF1F4B802BFA0105000B4573 /* HTML Templates */,
C425D4062B6D249E002A7B48 /* Preview Content */, C425D4062B6D249E002A7B48 /* Preview Content */,
@ -969,6 +972,14 @@
path = "HTML Templates"; path = "HTML Templates";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FF2B515F2C7E300500FFF126 /* SeedData */ = {
isa = PBXGroup;
children = (
FF2B51602C7E302C00FFF126 /* local.sqlite */,
);
path = SeedData;
sourceTree = "<group>";
};
FF39719B2B8DE04B004C4E75 /* Navigation */ = { FF39719B2B8DE04B004C4E75 /* Navigation */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1234,13 +1245,6 @@
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FFA4DA502C75E815002DAA31 /* Files */ = {
isa = PBXGroup;
children = (
);
path = Files;
sourceTree = "<group>";
};
FFBF41802BF73EA2001B24CB /* Event */ = { FFBF41802BF73EA2001B24CB /* Event */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1500,6 +1504,7 @@
C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */, C425D4082B6D249E002A7B48 /* Preview Assets.xcassets in Resources */,
FF44421C2BE39FA2008BBF0B /* Launch Screen.storyboard in Resources */, FF44421C2BE39FA2008BBF0B /* Launch Screen.storyboard in Resources */,
FF1F4B832BFA02A4000B4573 /* tournament-template.html in Resources */, FF1F4B832BFA02A4000B4573 /* tournament-template.html in Resources */,
FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */,
FF1F4B842BFA02A4000B4573 /* groupstagescore-template.html in Resources */, FF1F4B842BFA02A4000B4573 /* groupstagescore-template.html in Resources */,
FF1F4B852BFA02A4000B4573 /* player-template.html in Resources */, FF1F4B852BFA02A4000B4573 /* player-template.html in Resources */,
FF1F4B862BFA02A4000B4573 /* groupstagerow-template.html in Resources */, FF1F4B862BFA02A4000B4573 /* groupstagerow-template.html in Resources */,
@ -1955,7 +1960,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -2005,7 +2010,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -2197,9 +2202,10 @@
FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = { FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */ = {
isa = XCVersionGroup; isa = XCVersionGroup;
children = ( children = (
FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */,
FF3795612B9396D0004EA093 /* Model.xcdatamodel */, FF3795612B9396D0004EA093 /* Model.xcdatamodel */,
); );
currentVersion = FF3795612B9396D0004EA093 /* Model.xcdatamodel */; currentVersion = FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */;
path = PadelClubApp.xcdatamodeld; path = PadelClubApp.xcdatamodeld;
sourceTree = "<group>"; sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel; versionGroupType = wrapper.xcdatamodel;

@ -13,7 +13,13 @@ extension ImportedPlayer: PlayerHolder {
return getRank()?.femaleInMaleAssimilation 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? { var tournamentPlayed: Int? {
Int(tournamentCount) Int(tournamentCount)
@ -96,6 +102,18 @@ extension ImportedPlayer: PlayerHolder {
} }
return 0 return 0
} }
func getBirthYear() -> Int? {
if let birthYear {
return Int(birthYear)
} else {
return nil
}
}
func getProgression() -> Int {
return Int(progression)
}
} }
fileprivate extension Int { 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 { class PersistenceController: NSObject {
static let shared = PersistenceController() 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 var _model: NSManagedObjectModel?
private static func model(name: String) throws -> NSManagedObjectModel { private static func model(name: String) throws -> NSManagedObjectModel {
if _model == nil { if _model == nil {
@ -39,6 +45,12 @@ class PersistenceController: NSObject {
let localStoreFolderURL = storeFolderURL.appendingPathComponent("Local") let localStoreFolderURL = storeFolderURL.appendingPathComponent("Local")
let fileManager = FileManager.default 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) { for folderURL in [localStoreFolderURL] where !fileManager.fileExists(atPath: folderURL.path) {
do { do {
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) 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")) let container = NSPersistentContainer(name: "PadelClubApp", managedObjectModel: try! Self.model(name: "PadelClubApp"))
guard let localStoreDescription = container.persistentStoreDescriptions.first!.copy() as? NSPersistentStoreDescription else { 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.clubCode = data.clubCode.replaceCharactersFromSet(characterSet: .whitespaces)
importedPlayer.male = data.isMale importedPlayer.male = data.isMale
importedPlayer.importDate = importingDate importedPlayer.importDate = importingDate
importedPlayer.progression = Int64(data.progression)
importedPlayer.bestRank = data.bestRank?.formattedAsRawString()
importedPlayer.birthYear = data.birthYear?.formattedAsRawString()
} }
// 5 // 5

@ -20,6 +20,9 @@ class FederalPlayer: Decodable {
var clubCode: String var clubCode: String
var club: String var club: String
var isMale: Bool var isMale: Bool
var birthYear: Int?
var progression: Int
var bestRank: Int?
// MARK: - Nationnalite // MARK: - Nationnalite
@ -61,13 +64,13 @@ class FederalPlayer: Decodable {
let nationnalite = try container.decode(Nationnalite.self, forKey: .nationnalite) let nationnalite = try container.decode(Nationnalite.self, forKey: .nationnalite)
country = nationnalite.code country = nationnalite.code
//meilleurClassement = try container.decode(Int.self, forKey: .meilleurClassement) bestRank = try container.decodeIfPresent(Int.self, forKey: .meilleurClassement)
//anneeNaissance = try container.decode(Int.self, forKey: .anneeNaissance) birthYear = try container.decodeIfPresent(Int.self, forKey: .anneeNaissance)
clubCode = try container.decode(String.self, forKey: .codeClub) clubCode = try container.decode(String.self, forKey: .codeClub)
club = try container.decode(String.self, forKey: .nomClub) club = try container.decode(String.self, forKey: .nomClub)
ligue = try container.decode(String.self, forKey: .nomLigue) ligue = try container.decode(String.self, forKey: .nomLigue)
rank = try container.decode(Int.self, forKey: .rang) 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) let pointsAsInt = try? container.decodeIfPresent(Int.self, forKey: .points)
if let pointsAsInt { if let pointsAsInt {
points = Double(pointsAsInt) points = Double(pointsAsInt)
@ -84,7 +87,7 @@ class FederalPlayer: Decodable {
let pointsString = points != nil ? String(Int(points!)) : "" let pointsString = points != nil ? String(Int(points!)) : ""
let tournamentCountString = tournamentCount != nil ? String(tournamentCount!) : "" let tournamentCountString = tournamentCount != nil ? String(tournamentCount!) : ""
let strippedLicense = license.strippedLicense ?? "" 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 return line
} }
@ -167,6 +170,9 @@ class FederalPlayer: Decodable {
ligue = result[8] ligue = result[8]
clubCode = result[9] clubCode = result[9]
club = result[10] 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? { static func anonymousCount(mostRecentDateAvailable: Date?) async -> Int? {
@ -191,6 +197,8 @@ class FederalPlayer: Decodable {
} }
lastPlayerFetch.predicate = predicate lastPlayerFetch.predicate = predicate
let count = try? context.count(for: lastPlayerFetch) let count = try? context.count(for: lastPlayerFetch)
print("count", count)
do { do {
if let lr = try context.fetch(lastPlayerFetch).first?.rank { if let lr = try context.fetch(lastPlayerFetch).first?.rank {
let fetch = ImportedPlayer.fetchRequest() let fetch = ImportedPlayer.fetchRequest()

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import SwiftUI
protocol PlayerHolder { protocol PlayerHolder {
@ -24,6 +25,8 @@ protocol PlayerHolder {
var computedAge: Int? { get } var computedAge: Int? { get }
func getAssimilatedAsMaleRank() -> Int? func getAssimilatedAsMaleRank() -> Int?
func isNotFromCurrentDate() -> Bool func isNotFromCurrentDate() -> Bool
func getBirthYear() -> Int?
func getProgression() -> Int
} }
extension PlayerHolder { extension PlayerHolder {
@ -38,4 +41,16 @@ extension PlayerHolder {
func isAnonymous() -> Bool { func isAnonymous() -> Bool {
getFirstName().isEmpty && getLastName().isEmpty 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 { if let birthdate {
let components = birthdate.components(separatedBy: "/") let components = birthdate.components(separatedBy: "/")
if components.count == 3 { 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 age.count == 2 { //si l'année est sur 2 chiffres dans le fichier
if ageInt < 23 { if ageInt < 23 {
return year - 2000 - ageInt return year - 2000 - ageInt
@ -571,4 +573,12 @@ extension PlayerRegistration: PlayerHolder {
var male: Bool { var male: Bool {
isMalePlayer() isMalePlayer()
} }
func getBirthYear() -> Int? {
nil
}
func getProgression() -> Int {
0
}
} }

@ -23,4 +23,29 @@ extension Calendar {
return numberOfDaysBetween(date1, and: date2) == 0 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 { var pluralSuffix: String {
return self > 1 ? "s" : "" 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) 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 { var importingRawValue: String {
switch self { switch self {
case .unlisted: case .unlisted:
@ -185,9 +207,9 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable {
case .senior: case .senior:
return "Senior" return "Senior"
case .a45: case .a45:
return "45 ans" return "+45 ans"
case .a55: case .a55:
return "55 ans" return "+55 ans"
} }
} }
@ -243,21 +265,14 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable {
case .senior: case .senior:
return "Senior" return "Senior"
case .a45: case .a45:
return "45 ans" return "+45 ans"
case .a55: case .a55:
return "55 ans" return "+55 ans"
} }
} }
var tournamentDescriptionLabel: String { var tournamentDescriptionLabel: String {
switch self { return localizedLabel()
case .senior:
return ""
case .a45, .a55:
return "+" + localizedLabel()
default:
return localizedLabel()
}
} }
} }

@ -35,6 +35,7 @@ class SearchViewModel: ObservableObject, Identifiable {
@Published var selectedPlayers: Set<ImportedPlayer> = Set() @Published var selectedPlayers: Set<ImportedPlayer> = Set()
@Published var filterSelectionEnabled: Bool = false @Published var filterSelectionEnabled: Bool = false
@Published var isPresented: Bool = false @Published var isPresented: Bool = false
@Published var selectedAgeCategory: FederalTournamentAge = .unlisted
var mostRecentDate: Date? = nil var mostRecentDate: Date? = nil
@ -90,11 +91,15 @@ class SearchViewModel: ObservableObject, Identifiable {
} }
func showIndex() -> Bool { func showIndex() -> Bool {
if (dataSet == .national || dataSet == .ligue) { return false } if (dataSet == .national || dataSet == .ligue) { return isFiltering() }
if filterOption == .all { return false } if filterOption == .all { return isFiltering() }
return true return true
} }
func isFiltering() -> Bool {
searchText.isEmpty == false || tokens.isEmpty == false || hideAssimilation || selectedAgeCategory != .unlisted
}
func prompt(forDataSet: DataSet) -> String { func prompt(forDataSet: DataSet) -> String {
switch forDataSet { switch forDataSet {
case .national: case .national:
@ -195,6 +200,13 @@ class SearchViewModel: ObservableObject, Identifiable {
} else { } 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 {
predicates.append(NSPredicate(format: "birthYear == 0"))
} else if let birthYear = Int(canonicalVersionWithoutPunctuation) {
predicates.append(NSPredicate(format: "birthYear == %@", birthYear.formattedAsRawString()))
}
} }
if predicates.isEmpty { if predicates.isEmpty {
@ -222,6 +234,17 @@ class SearchViewModel: ObservableObject, Identifiable {
predicates.append(NSPredicate(format: "assimilation == %@", "Non")) 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 { switch dataSet {
case .national: case .national:
break break
@ -267,6 +290,7 @@ enum SearchToken: String, CaseIterable, Identifiable {
case rankMoreThan = "rang >" case rankMoreThan = "rang >"
case rankLessThan = "rang <" case rankLessThan = "rang <"
case rankBetween = "rang <>" case rankBetween = "rang <>"
case age = "âge sportif"
var id: String { var id: String {
rawValue 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." return "Taper un nombre pour chercher les joueurs ayant un classement inférieur ou égale."
case .rankBetween: 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"
} }
} }
@ -297,6 +323,8 @@ enum SearchToken: String, CaseIterable, Identifiable {
return "Chercher un rang" return "Chercher un rang"
case .rankBetween: case .rankBetween:
return "Chercher une intervalle de classement" 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 à" return "Rang inférieur ou égale à"
case .rankBetween: case .rankBetween:
return "Rang entre deux valeurs" return "Rang entre deux valeurs"
case .age:
return "Année de naissance"
} }
} }
@ -327,6 +357,8 @@ enum SearchToken: String, CaseIterable, Identifiable {
return "Rang ≤" return "Rang ≤"
case .rankBetween: case .rankBetween:
return "Rang ≥,≤" return "Rang ≥,≤"
case .age:
return "Né(e) en"
} }
} }
@ -342,6 +374,8 @@ enum SearchToken: String, CaseIterable, Identifiable {
return "figure.racquetball" return "figure.racquetball"
case .rankBetween: case .rankBetween:
return "figure.racquetball" return "figure.racquetball"
case .age:
return "figure.racquetball"
} }
} }
@ -357,6 +391,8 @@ enum SearchToken: String, CaseIterable, Identifiable {
return "figure.racquetball" return "figure.racquetball"
case .rankBetween: case .rankBetween:
return "figure.racquetball" return "figure.racquetball"
case .age:
return "figure.racquetball"
} }
} }
} }
@ -387,13 +423,13 @@ enum DataSet: Int, Identifiable {
var tokens: [SearchToken] { var tokens: [SearchToken] {
switch self { switch self {
case .national: case .national:
return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween] return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween, .age]
case .ligue: case .ligue:
return [.club, .rankMoreThan, .rankLessThan, .rankBetween] return [.club, .rankMoreThan, .rankLessThan, .rankBetween, .age]
case .club: case .club:
return [.rankMoreThan, .rankLessThan, .rankBetween] return [.rankMoreThan, .rankLessThan, .rankBetween, .age]
case .favoritePlayers, .favoriteClubs: case .favoritePlayers, .favoriteClubs:
return [.rankMoreThan, .rankLessThan, .rankBetween] return [.rankMoreThan, .rankLessThan, .rankBetween, .age]
} }
} }
} }
@ -403,6 +439,7 @@ enum SortOption: Int, CaseIterable, Identifiable {
case rank case rank
case tournamentCount case tournamentCount
case points case points
case progression
var id: Int { self.rawValue } var id: Int { self.rawValue }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@ -415,6 +452,8 @@ enum SortOption: Int, CaseIterable, Identifiable {
return "Tournoi" return "Tournoi"
case .points: case .points:
return "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)] return [SortDescriptor(\ImportedPlayer.tournamentCount, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
case .points: 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)]
} }
} }
} }

@ -186,7 +186,7 @@ struct MainView: View {
importObserver.checkingFiles = false importObserver.checkingFiles = false
if lastDataSource == nil || (dataStore.monthData.first(where: { $0.monthKey == "07-2024" }) == nil) { if lastDataSource == nil || (dataStore.monthData.first(where: { $0.monthKey == "07-2024" }) == nil) {
await _downloadPreviousDate() // await _downloadPreviousDate()
await _importMandatoryData() await _importMandatoryData()
} }
@ -226,7 +226,10 @@ struct MainView: View {
let mandatoryKey = "07-2024" let mandatoryKey = "07-2024"
if dataStore.monthData.first(where: { $0.monthKey == mandatoryKey }) == nil, let importingDate = URL.importDateFormatter.date(from: mandatoryKey) { if dataStore.monthData.first(where: { $0.monthKey == mandatoryKey }) == nil, let importingDate = URL.importDateFormatter.date(from: mandatoryKey) {
print("importing mandatory july data") 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() Spacer()
Text(monthData.total().formatted() + " joueurs") 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,6 +11,7 @@ struct ImportedPlayerView: View {
let player: PlayerHolder let player: PlayerHolder
var index: Int? = nil var index: Int? = nil
var showFemaleInMaleAssimilation: Bool = false var showFemaleInMaleAssimilation: Bool = false
@State var showProgression: Bool = false
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -53,6 +54,15 @@ struct ImportedPlayerView: View {
} }
} }
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 { if let pts = player.getPoints(), pts > 0 {
HStack(alignment: .lastTextBaseline, spacing: 0) { HStack(alignment: .lastTextBaseline, spacing: 0) {
Text(pts.formatted()).font(.title3) Text(pts.formatted()).font(.title3)
@ -68,7 +78,7 @@ struct ImportedPlayerView: View {
} }
} }
if let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank(), showFemaleInMaleAssimilation { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
Text("(") Text("(")
Text(assimilatedAsMaleRank.formatted()) Text(assimilatedAsMaleRank.formatted())

@ -19,6 +19,8 @@ struct SelectablePlayerListView: View {
let contentUnavailableAction: ContentUnavailableAction? let contentUnavailableAction: ContentUnavailableAction?
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(ImportObserver.self) private var importObserver: ImportObserver
@StateObject private var searchViewModel: SearchViewModel @StateObject private var searchViewModel: SearchViewModel
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@ -26,8 +28,6 @@ struct SelectablePlayerListView: View {
dataStore.appSettings.lastDataSource dataStore.appSettings.lastDataSource
} }
@AppStorage("importingFiles") var importingFiles: Bool = false
@State private var searchText: String = "" @State private var searchText: String = ""
var mostRecentDate: Date? { var mostRecentDate: Date? {
guard let lastDataSource else { return nil } guard let lastDataSource else { return nil }
@ -61,15 +61,76 @@ struct SelectablePlayerListView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
if importingFiles == false { if importObserver.isImportingFile() == false {
if searchViewModel.filterSelectionEnabled == false { if searchViewModel.filterSelectionEnabled == false {
Picker(selection: $searchViewModel.filterOption) { VStack {
ForEach(PlayerFilterOption.allCases, id: \.self) { scope in HStack {
Text(scope.icon().capitalized) 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(.bottom)
.padding(.horizontal) .padding(.horizontal)
.background(Material.thick) .background(Material.thick)
@ -119,9 +180,9 @@ struct SelectablePlayerListView: View {
} }
} }
} }
.id(importingFiles) .id(importObserver.currentImportDate)
.overlay { .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)")) 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 searchViewModel.filterSelectionEnabled = false
} }
} }
.onChange(of: searchViewModel.tokens) {
if searchViewModel.tokens.first == .age {
searchViewModel.selectedAgeCategory = .unlisted
}
}
.toolbar { .toolbar {
if searchViewModel.allowMultipleSelection { if searchViewModel.allowMultipleSelection {
ToolbarItemGroup(placement: .topBarLeading) { ToolbarItemGroup(placement: .topBarLeading) {
@ -341,8 +407,9 @@ struct MySearchView: View {
.id(UUID()) .id(UUID())
} else { } else {
Section { Section {
ForEach(players) { player in ForEach(players.indices, id: \.self) { index in
ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) let player = players[index]
ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation)
} }
} header: { } header: {
if players.isEmpty == false { if players.isEmpty == false {
@ -365,7 +432,7 @@ struct MySearchView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.buttonStyle(.plain) .buttonStyle(.plain)
} else { } else {
ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation)
} }
} }
} header: { } header: {
@ -383,45 +450,6 @@ struct MySearchView: View {
HStack { HStack {
Text("\(players.count.formatted()) \( searchViewModel.filterOption.localizedPlayerLabel)\( players.count.pluralSuffix)") Text("\(players.count.formatted()) \( searchViewModel.filterOption.localizedPlayerLabel)\( players.count.pluralSuffix)")
Spacer() 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