diff --git a/PadelClub/Data/DrawLog.swift b/PadelClub/Data/DrawLog.swift index 29cf948..8673aaf 100644 --- a/PadelClub/Data/DrawLog.swift +++ b/PadelClub/Data/DrawLog.swift @@ -13,7 +13,7 @@ import LeStorage final class DrawLog: ModelObject, Storable { static func resourceName() -> String { return "draw-logs" } static func tokenExemptedMethods() -> [HTTPMethod] { return [] } - static func filterByStoreIdentifier() -> Bool { return false } + static func filterByStoreIdentifier() -> Bool { return true } static var relationshipNames: [String] = [] var id: String = Store.randomId() @@ -22,14 +22,16 @@ final class DrawLog: ModelObject, Storable { var drawSeed: Int var drawMatchIndex: Int var drawTeamPosition: TeamPosition + var drawType: DrawType - internal init(id: String = Store.randomId(), tournament: String, drawDate: Date = Date(), drawSeed: Int, drawMatchIndex: Int, drawTeamPosition: TeamPosition) { + internal init(id: String = Store.randomId(), tournament: String, drawDate: Date = Date(), drawSeed: Int, drawMatchIndex: Int, drawTeamPosition: TeamPosition, drawType: DrawType) { self.id = id self.tournament = tournament self.drawDate = drawDate self.drawSeed = drawSeed self.drawMatchIndex = drawMatchIndex self.drawTeamPosition = drawTeamPosition + self.drawType = drawType } func tournamentObject() -> Tournament? { @@ -48,24 +50,34 @@ final class DrawLog: ModelObject, Storable { } func exportedDrawLog() -> String { - [drawDate.localizedDate(), localizedDrawLogLabel(), localizedDrawBranch()].joined(separator: " ") + [drawType.localizedDrawType(), drawDate.localizedDate(), localizedDrawLogLabel(), localizedDrawBranch()].filter({ $0.isEmpty == false }).joined(separator: " ") } func localizedDrawSeedLabel() -> String { - return "Tête de série #\(drawSeed + 1)" + return "\(drawType.localizedDrawType()) #\(drawSeed + 1)" } func localizedDrawLogLabel() -> String { - return [localizedDrawSeedLabel(), positionLabel()].joined(separator: " -> ") + return [localizedDrawSeedLabel(), positionLabel()].filter({ $0.isEmpty == false }).joined(separator: " -> ") } func localizedDrawBranch() -> String { - drawTeamPosition.localizedBranchLabel() + switch drawType { + case .seed: + return drawTeamPosition.localizedBranchLabel() + default: + return "" + } } func drawMatch() -> Match? { - let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawMatchIndex) - return tournamentStore.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex }) + switch drawType { + case .seed: + let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawMatchIndex) + return tournamentStore.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex }) + default: + return nil + } } func positionLabel() -> String { @@ -94,6 +106,32 @@ final class DrawLog: ModelObject, Storable { case _drawSeed = "drawSeed" case _drawMatchIndex = "drawMatchIndex" case _drawTeamPosition = "drawTeamPosition" + case _drawType = "drawType" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + id = try container.decode(String.self, forKey: ._id) + tournament = try container.decode(String.self, forKey: ._tournament) + drawDate = try container.decode(Date.self, forKey: ._drawDate) + drawSeed = try container.decode(Int.self, forKey: ._drawSeed) + drawMatchIndex = try container.decode(Int.self, forKey: ._drawMatchIndex) + drawTeamPosition = try container.decode(TeamPosition.self, forKey: ._drawTeamPosition) + drawType = try container.decodeIfPresent(DrawType.self, forKey: ._drawType) ?? .seed + } + + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(id, forKey: ._id) + try container.encode(tournament, forKey: ._tournament) + try container.encode(drawDate, forKey: ._drawDate) + try container.encode(drawSeed, forKey: ._drawSeed) + try container.encode(drawMatchIndex, forKey: ._drawMatchIndex) + try container.encode(drawTeamPosition, forKey: ._drawTeamPosition) + try container.encode(drawType, forKey: ._drawType) } func insertOnServer() throws { @@ -101,3 +139,20 @@ final class DrawLog: ModelObject, Storable { } } + +enum DrawType: Int, Codable { + case seed + case groupStage + case court + + func localizedDrawType() -> String { + switch self { + case .seed: + return "Tête de série" + case .groupStage: + return "Poule" + case .court: + return "Terrain" + } + } +} diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 1d8d414..14909a2 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -140,7 +140,7 @@ final class TeamRegistration: ModelObject, Storable { } if let tournament = tournamentObject() { if let index = index(in: tournament.selectedSortedTeams()) { - let drawLog = DrawLog(tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index, drawTeamPosition: teamPosition) + let drawLog = DrawLog(tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index, drawTeamPosition: teamPosition, drawType: .seed) do { try tournamentStore.drawLogs.addOrUpdate(instance: drawLog) } catch { diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index deb82db..f67228b 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -64,9 +64,10 @@ class FileImportManager { importedPlayer.firstName = firstName.trimmed.capitalized } } - playersLeft.removeAll(where: { $0.lastName.isEmpty == false }) } }) + + players = playersLeft } func foundInWomenData(license: String?) -> Bool { diff --git a/PadelClub/Utils/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift index ac06446..80c330c 100644 --- a/PadelClub/Utils/SourceFileManager.swift +++ b/PadelClub/Utils/SourceFileManager.swift @@ -60,9 +60,9 @@ class SourceFileManager { } } - func exportToCSV(players: [FederalPlayer], sourceFileType: SourceFile, date: Date) { + func exportToCSV(_ prefix: String = "", players: [FederalPlayer], sourceFileType: SourceFile, date: Date) { let lastDateString = URL.importDateFormatter.string(from: date) - let dateString = ["CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].joined(separator: "-") + "." + "csv" + let dateString = [prefix, "CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv" let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)") diff --git a/PadelClub/Views/GroupStage/GroupStageQualificationManagerView.swift b/PadelClub/Views/GroupStage/GroupStageQualificationManagerView.swift new file mode 100644 index 0000000..671a96a --- /dev/null +++ b/PadelClub/Views/GroupStage/GroupStageQualificationManagerView.swift @@ -0,0 +1,72 @@ +// +// GroupStageQualificationManager.swift +// PadelClub +// +// Created by razmig on 05/11/2024. +// + + +class GroupStageQualificationManager { + private let tournament: Tournament + private let tournamentStore: TournamentStore + + init(tournament: Tournament, tournamentStore: TournamentStore) { + self.tournament = tournament + self.tournamentStore = tournamentStore + } + + func qualificationSection() -> some View { + guard tournament.groupStageAdditionalQualified > 0 else { return EmptyView() } + + let name = "\(tournament.qualifiedPerGroupStage + 1).ordinalFormatted()" + let missingQualifiedFromGroupStages = tournament.missingQualifiedFromGroupStages() + + return Section { + NavigationLink { + SpinDrawView( + drawees: missingQualifiedFromGroupStages.isEmpty + ? tournament.groupStageAdditionalQualifiedPreDraw() + : ["Qualification d'un \(name) de poule"], + segments: missingQualifiedFromGroupStages.isEmpty + ? tournament.groupStageAdditionalLeft() + : missingQualifiedFromGroupStages + ) { results in + if !missingQualifiedFromGroupStages.isEmpty { + self.handleDrawResults(results, missingQualifiedFromGroupStages) + } + } + } label: { + Label { + Text("Qualifier un \(name) de poule par tirage au sort") + } icon: { + Image(systemName: "exclamationmark.circle.fill") + .foregroundStyle(.logoBackground) + } + } + .disabled(tournament.moreQualifiedToDraw() == 0) + } footer: { + footerText(missingQualifiedFromGroupStages.isEmpty) + } + } + + private func handleDrawResults(_ results: [DrawResult], _ missingQualifiedFromGroupStages: [Team]) { + results.forEach { drawResult in + var team = missingQualifiedFromGroupStages[drawResult.drawIndex] + team.qualified = true + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } + } + + private func footerText(_ noMoreTeams: Bool) -> Text { + if tournament.moreQualifiedToDraw() == 0 { + return Text("Aucune équipe supplémentaire à qualifier. Vous pouvez en rajouter en modifiant le paramètre dans structure.") + } else if noMoreTeams { + return Text("Aucune équipe supplémentaire à tirer au sort. Attendez la fin des poules.") + } + return Text("") + } +} diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index 95311c8..56e46d8 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -7,6 +7,7 @@ import SwiftUI import LeStorage +import Foundation struct PadelClubView: View { @State private var uuid: UUID = UUID() @@ -74,9 +75,14 @@ struct PadelClubView: View { print("before anonymousPlayers.count", anonymousPlayers.count) FileImportManager.shared.updatePlayers(isMale: fileURL.manData, players: &anonymousPlayers) - print("after anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } + print("after local anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count) + + await fetchPlayersDataSequentially(for: &anonymousPlayers) + + print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } .count) SourceFileManager.shared.exportToCSV(players: okPlayers + anonymousPlayers, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) + SourceFileManager.shared.exportToCSV("anonymes", players: anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) } catch { Logger.error(error) } @@ -241,3 +247,71 @@ struct PadelClubView: View { //#Preview { // PadelClubView() //} + +// Function to fetch data for a single license ID +func fetchPlayerData(for licenseID: String) async throws -> [Player]? { + guard let url = URL(string: "https://beach-padel.app.fft.fr/beachja/rechercheJoueur/licencies?idHomologation=82477107&numeroLicence=\(licenseID)") else { + throw URLError(.badURL) + } + + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.setValue("application/json, text/javascript, */*; q=0.01", forHTTPHeaderField: "Accept") + request.setValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site") + request.setValue("fr-FR,fr;q=0.9", forHTTPHeaderField: "Accept-Language") + request.setValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding") + request.setValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode") + request.setValue("beach-padel.app.fft.fr", forHTTPHeaderField: "Host") + request.setValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15", forHTTPHeaderField: "User-Agent") + request.setValue("keep-alive", forHTTPHeaderField: "Connection") + request.setValue("https://beach-padel.app.fft.fr/beachja/competitionFiche/inscrireEquipe?identifiantHomologation=82477107", forHTTPHeaderField: "Referer") + request.setValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With") + + // Add cookies if needed (example cookie header value shown, replace with valid cookies) + request.setValue("JSESSIONID=F4ED2A1BCF3CD2694FE0B111B8027999; AWSALB=JoZEC/+cnAzmCdbbm3Vuc4CtMGx8BvbveFx+RBRuj8dQCQD52C9iDDbL/OVm98uMb7vc8Jv6/bVPkaByXWmOZmSGwAsN2s8/jt6W5L8QGz7omzNbYF01kvqffRvo; AWSALBCORS=JoZEC/+cnAzmCdbbm3Vuc4CtMGx8BvbveFx+RBRuj8dQCQD52C9iDDbL/OVm98uMb7vc8Jv6/bVPkaByXWmOZmSGwAsN2s8/jt6W5L8QGz7omzNbYF01kvqffRvo; datadome=KlbIdnrCgaY1zLVIZ5CfLJm~KXv9_YnXGhaQdqMEn6Ja9R6imBH~vhzmyuiLxGi1D0z90v5x2EiGDvQ7zsw~fajWLbOupFEajulc86PSJ7RIHpOiduCQ~cNoITQYJOXa; tc_cj_v2=m_iZZZ%22**%22%27%20ZZZKQLNQOPLOSLJOZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQLQJRKOQKSMOZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQLQJRKOQMSLNZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQLQJRKOQNSJMZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQLQJRKOSJMLJZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQLRPQMQQNRQRZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQLRPQNKSLOMSZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQLSNSOPMSOPJZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQMJQSRLJSOOJZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMJRJPJMSSKRZZZ%5D; tc_cj_v2_cmp=; tc_cj_v2_med=; tCdebugLib=1; incap_ses_2222_2712217=ui9wOOAjNziUTlU3gCHWHtv/KWcAAAAAhSzbpyITRp7YwRT3vJB2vg==; incap_ses_2224_2712217=NepDAr2kUDShMiCJaDzdHqbjKWcAAAAA0kLlk3lgvGnwWSTMceZoEw==; xtan=-; xtant=1; incap_ses_1350_2712217=g+XhSJRwOS8JlWTYCSq8EtOBJGcAAAAAffg2IobkPUW2BtvgJGHbMw==; TCSESSION=124101910177775608913; nlbi_2712217=jnhtOC5KDiLvfpy/b9lUTgAAAAA7zduh8JyZOVrEfGsEdFlq; TCID=12481811494814553052; xtvrn=$548419$; TCPID=12471746148351334672; visid_incap_2712217=PSfJngzoSuiowsuXXhvOu5K+7mUAAAAAQUIPAAAAAAAleL9ldvN/FC1VykkU9ret; SessionStatId=10.91.140.42.1662124965429001", forHTTPHeaderField: "Cookie") + + let (data, _) = try await URLSession.shared.data(for: request) + let decoder = JSONDecoder() + + // Debug: Print raw JSON data for inspection + if let jsonString = String(data: data, encoding: .utf8) { + print("Raw JSON response: \(jsonString)") + } + + // Decode the response + let response = try decoder.decode(Response.self, from: data) + let players = response.object.listeJoueurs + + // Cast the JSON object to [String: Any] dictionary + return players +} + +// Function to fetch data for multiple license IDs using TaskGroup +func fetchPlayersDataSequentially(for licenseIDs: inout [FederalPlayer]) async { + for licenseID in licenseIDs.filter({ $0.firstName.isEmpty && $0.lastName.isEmpty }) { + do { + if let playerData = try await fetchPlayerData(for: licenseID.license)?.first { + licenseID.lastName = playerData.nom + licenseID.firstName = playerData.prenom + } + } catch { + print(error) + } + } +} + + +struct Player: Codable { + let licence: Int + let nom: String + let prenom: String + let sexe: String +} + +struct Response: Codable { + let object: PlayerList +} + +struct PlayerList: Codable { + let listeJoueurs: [Player] +} diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 0024d88..42a45b4 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -86,7 +86,7 @@ struct RoundSettingsView: View { if previewAvailable { NavigationLink { - PreviewBracketPositionView(seeds: tournament.seeds(), drawLogs: tournament.drawLogs()) + PreviewBracketPositionView(seeds: tournament.seeds(), drawLogs: tournament.drawLogs().filter({ $0.drawType == .seed })) } label: { Text("Aperçu du repositionnement") } diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index be746d3..c4426d7 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -76,6 +76,10 @@ struct TournamentCellView: View { // .frame(width: 2) VStack(alignment: .leading, spacing: 0.0) { if let tournament = tournament as? Tournament { +#if targetEnvironment(simulator) + Text(tournament.id) +#endif + HStack { Text(tournament.locationLabel(displayStyle)) .lineLimit(1) diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index 40f0feb..15887ad 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -378,7 +378,7 @@ final class ServerDataTests: XCTestCase { return } - let drawLog = DrawLog(tournament: tournamentId, drawSeed: 1, drawMatchIndex: 1, drawTeamPosition: .two) + let drawLog = DrawLog(tournament: tournamentId, drawSeed: 1, drawMatchIndex: 1, drawTeamPosition: .two, drawType: .court) let d: DrawLog = try await StoreCenter.main.service().post(drawLog) assert(d.tournament == drawLog.tournament) @@ -386,6 +386,7 @@ final class ServerDataTests: XCTestCase { assert(d.drawSeed == drawLog.drawSeed) assert(d.drawTeamPosition == drawLog.drawTeamPosition) assert(d.drawMatchIndex == drawLog.drawMatchIndex) + assert(d.drawType == drawLog.drawType) } }