diff --git a/PadelClub/Data/Federal/FederalPlayer.swift b/PadelClub/Data/Federal/FederalPlayer.swift index a4b2d42..059f7aa 100644 --- a/PadelClub/Data/Federal/FederalPlayer.swift +++ b/PadelClub/Data/Federal/FederalPlayer.swift @@ -7,7 +7,7 @@ import Foundation -struct FederalPlayer { +struct FederalPlayer: Decodable { var rank: Int var lastName: String var firstName: String @@ -21,6 +21,74 @@ struct FederalPlayer { var club: String var isMale: Bool + + // MARK: - Nationnalite + struct Nationnalite: Hashable, Codable { + let code, codeFov: String + } + + init(from decoder: Decoder) throws { + enum CodingKeys: String, CodingKey { + case nom + case prenom + case licence + case meilleurClassement + case nationnalite + case anneeNaissance + case codeClub + case nomClub + case nomLigue + case rang + case progression + case points + case nombreDeTournois + case assimile + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + + isMale = (decoder.userInfo[.maleData] as? Bool) == true + + let _lastName = try container.decode(String.self, forKey: .nom) + let _firstName = try container.decode(String.self, forKey: .prenom) + lastName = _lastName + firstName = _firstName + if let lic = try? container.decodeIfPresent(Int.self, forKey: .licence) { + license = String(lic) + } else { + license = "" + } + + 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) + 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) + let pointsAsInt = try? container.decodeIfPresent(Int.self, forKey: .points) + if let pointsAsInt { + points = Double(pointsAsInt) + } else { + points = nil + } + tournamentCount = try? container.decodeIfPresent(Int.self, forKey: .nombreDeTournois) + let assimile = try container.decode(Bool.self, forKey: .assimile) + assimilation = assimile ? "Oui" : "Non" + fullNameCanonical = _lastName.canonicalVersion + " " + _firstName.canonicalVersion + + } + + func exportToCSV() -> String { + let pointsString = points != nil ? Int(points!).formatted() : "" + let tournamentCountString = tournamentCount != nil ? tournamentCount!.formatted() : "" + let strippedLicense = license.strippedLicense ?? "" + let line = ";\(rank);\(lastName);\(firstName);\(country);\(strippedLicense);\(pointsString);\(assimilation);\(tournamentCountString);\(ligue);\(clubCode);\(club);" + return line + } + var fullNameCanonical: String /* @@ -115,3 +183,7 @@ struct FederalPlayer { } } + +extension CodingUserInfoKey { + static let maleData = Self(rawValue: "maleData")! +} diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 2753a40..b75e7b9 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -870,11 +870,10 @@ class Tournament : ModelObject, Storable { let lastRankMan = currentMonthData()?.maleUnrankedValue let lastRankWoman = currentMonthData()?.femaleUnrankedValue + let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate }) + let sources = dataURLs.map { CSVParser(url: $0) } try await unsortedPlayers().concurrentForEach { player in - let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate }) - let sources = dataURLs.map { CSVParser(url: $0) } - try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0) } } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index d9adf4c..d7ce2fc 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -6,6 +6,7 @@ // import Foundation +import LeStorage class FileImportManager { static let shared = FileImportManager() @@ -147,7 +148,7 @@ class FileImportManager { } func importingChunkOfPlayers(_ players: [FederalPlayer], importingDate: Date) async { - for chunk in players.chunked(into: 1000) { + for chunk in players.chunked(into: 2000) { await PersistenceController.shared.batchInsertPlayers(chunk, importingDate: importingDate) } } diff --git a/PadelClub/Utils/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift index 0c1b68a..1a6fc97 100644 --- a/PadelClub/Utils/SourceFileManager.swift +++ b/PadelClub/Utils/SourceFileManager.swift @@ -6,6 +6,7 @@ // import Foundation +import LeStorage class SourceFileManager { static let shared = SourceFileManager() @@ -60,6 +61,26 @@ class SourceFileManager { } } + func exportToCSV(players: [FederalPlayer], sourceFileType: SourceFile, date: Date) { + let lastDateString = URL.importDateFormatter.string(from: date) + let dateString = ["CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].joined(separator: "-") + "." + "csv" + + let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! + let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)") + var csvText : String = "" + for player in players { + csvText.append(player.exportToCSV() + "\n") + } + + do { + try csvText.write(to: destinationFileUrl, atomically: true, encoding: .utf8) + print("CSV file exported successfully.") + } catch { + print("Error writing CSV file:", error) + Logger.error(error) + } + } + func fetchData(fromDate current: Date) async { let lastStringDate = URL.importDateFormatter.string(from: current) @@ -148,6 +169,13 @@ class SourceFileManager { } } + func jsonFiles() -> [URL] { + let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in + url.pathExtension == "json" + }) + return allJSONFiles + } + var allFiles: [URL] { let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in url.pathExtension == "csv" diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 2338077..52479a1 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -126,11 +126,7 @@ struct MainView: View { Task { let lastDataSource = await FileImportManager.shared.importDataFromFFT() dataStore.appSettings.lastDataSource = lastDataSource - do { - try dataStore.appSettingsStorage.write() - } catch { - Logger.error(error) - } + dataStore.appSettingsStorage.write() if let lastDataSource, let mostRecentDate = URL.importDateFormatter.date(from: lastDataSource) { await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) } diff --git a/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift b/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift index 3cd3c7a..f4401fd 100644 --- a/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift @@ -47,8 +47,55 @@ struct PadelClubView: View { Text("Classement mensuel utilisé") } } + + if let mostRecentDateAvailable = SourceFileManager.shared.mostRecentDateAvailable, _lastDataSourceDate.isEarlierThan(mostRecentDateAvailable) { + Section { + RowButtonView("Importer \(URL.importDateFormatter.string(from: mostRecentDateAvailable))") { + _startImporting() + } + } + } + + #if targetEnvironment(simulator) + /* + ["36435", "BOUNOUA", "walid", "France", "3311600", "15,00", "Non", "2", "AUVERGNE RHONE-ALPES", "50 73 0046", "CHAMBERY TC"] + ["36435", "BRUL…", "Romain", "France", "2993139", "15,00", "Non", "2", "NOUVELLE AQUITAINE", "59 33 0447", "SAINT LOUBES TC"] + + */ + Section { + RowButtonView("Exporter en csv") { + for fileURL in SourceFileManager.shared.jsonFiles() { + let decoder = JSONDecoder() + decoder.userInfo[.maleData] = fileURL.manData + + do { + let data = try Data(contentsOf: fileURL) + let players = try decoder.decode([FederalPlayer].self, from: data) + SourceFileManager.shared.exportToCSV(players: players, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) + } catch { + Logger.error(error) + } + } + } + } + + #endif } + if importingFiles { + ContentUnavailableView("Importation en cours", systemImage: "server.rack", description: Text("Une importation des données fédérales publiques est en cours, veuillez patienter.")) + } else if (players.isEmpty || lastDataSource == nil) { + ContentUnavailableView { + Label("Aucun joueur importé", systemImage: "person.slash") + } description: { + Text("Padel Club peut importer toutes les données publiques de la FFT concernant tous les compétiteurs et compétitrices.") + } actions: { + RowButtonView("Démarrer l'importation") { + _startImporting() + } + } + } + let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed() ForEach(monthData) { monthData in Section { @@ -72,20 +119,6 @@ struct PadelClubView: View { Text(monthData.monthKey) } } - - if importingFiles { - ContentUnavailableView("Importation en cours", systemImage: "server.rack", description: Text("Une importation des données fédérales publiques est en cours, veuillez patienter.")) - } else if (players.isEmpty || lastDataSource == nil) { - ContentUnavailableView { - Label("Aucun joueur importé", systemImage: "person.slash") - } description: { - Text("Padel Club peut importer toutes les données publiques de la FFT concernant tous les compétiteurs et compétitrices.") - } actions: { - RowButtonView("Démarrer l'importation") { - _startImporting() - } - } - } } .task { await self._checkSourceFileAvailability() @@ -130,11 +163,7 @@ struct PadelClubView: View { Task { let lastDataSource = await FileImportManager.shared.importDataFromFFT() dataStore.appSettings.lastDataSource = lastDataSource - do { - try dataStore.appSettingsStorage.write() - } catch { - Logger.error(error) - } + dataStore.appSettingsStorage.write() if let lastDataSource, let mostRecentDate = URL.importDateFormatter.date(from: lastDataSource) { await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: mostRecentDate) }