From fdd32440c98c15e98ad3bdc0dde7f00efbd5b0b2 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 12 Jun 2025 08:02:40 +0200 Subject: [PATCH] fix some import stuff add event contact sharing --- PadelClub/Utils/FileImportManager.swift | 14 +- PadelClub/Utils/HtmlGenerator.swift | 10 +- .../Views/Calling/GroupStageCallingView.swift | 8 ++ .../Views/Cashier/Event/EventStatusView.swift | 120 +++++++++++++++++- PadelClub/Views/Components/StepperView.swift | 2 +- .../Navigation/Umpire/PadelClubView.swift | 8 +- PadelClub/Views/Player/PlayerDetailView.swift | 12 +- PadelClub/Views/Team/EditingTeamView.swift | 7 +- .../ConsolationTournamentImportView.swift | 2 +- .../Views/Tournament/FileImportView.swift | 33 +++-- 10 files changed, 187 insertions(+), 29 deletions(-) diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index 3e9c1d4..8ca650f 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -132,14 +132,16 @@ class FileImportManager { let weight: Int let tournamentCategory: TournamentCategory let tournamentAgeCategory: FederalTournamentAge + let tournamentLevel: TournamentLevel let previousTeam: TeamRegistration? var registrationDate: Date? = nil var name: String? = nil - init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, tournamentAgeCategory: FederalTournamentAge, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) { + init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, tournamentAgeCategory: FederalTournamentAge, tournamentLevel: TournamentLevel, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) { self.players = Set(players) self.tournamentCategory = tournamentCategory self.tournamentAgeCategory = tournamentAgeCategory + self.tournamentLevel = tournamentLevel self.name = name self.previousTeam = previousTeam if players.count < 2 { @@ -152,7 +154,7 @@ class FileImportManager { } let significantPlayerCount = 2 let pl = players.prefix(significantPlayerCount).map { $0.computedRank } - let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 90_000 : 10_000) }).prefix(significantPlayerCount) + let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 90_415 : 10_000) }).prefix(significantPlayerCount) self.weight = pl.reduce(0,+) + missingPl.reduce(0,+) } else { self.weight = players.map { $0.computedRank }.reduce(0,+) @@ -314,7 +316,7 @@ class FileImportManager { let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { - let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), tournament: tournament) results.append(team) } } @@ -377,7 +379,7 @@ class FileImportManager { let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { - let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) + let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), tournament: tournament) results.append(team) } } @@ -426,7 +428,7 @@ class FileImportManager { return nil } - let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament) + let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament) results.append(team) } } @@ -486,7 +488,7 @@ class FileImportManager { } } - return TeamHolder(players: players, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: nil, name: teamName, tournament: tournament) + return TeamHolder(players: players, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, tournamentLevel: tournament.tournamentLevel, previousTeam: nil, name: teamName, tournament: tournament) } return results } diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index 656c8aa..6ec685a 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -62,6 +62,8 @@ class HtmlGenerator: ObservableObject { func generateWebView(webView: WKWebView) { self.webView = webView +#if targetEnvironment(simulator) +#else self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in print("evaluateJavaScript", "readystage", complete, error) if complete != nil { @@ -78,9 +80,12 @@ class HtmlGenerator: ObservableObject { }) } }) +#endif } func generateGroupStage(webView: WKWebView) { +#if targetEnvironment(simulator) +#else webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in @@ -115,7 +120,7 @@ class HtmlGenerator: ObservableObject { }) } }) - +#endif } func buildPDF() { @@ -149,6 +154,8 @@ class HtmlGenerator: ObservableObject { } func createPage() { +#if targetEnvironment(simulator) +#else let config = WKPDFConfiguration() config.rect = rects[pdfDocument.pageCount] webView.createPDF(configuration: config){ result in @@ -167,6 +174,7 @@ class HtmlGenerator: ObservableObject { self.completionHandler?(.failure(error)) } } +#endif } func generateHtml() -> String { diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index bfade2a..42a7497 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -45,6 +45,14 @@ struct GroupStageCallingView: View { PlayersWithoutContactView(players: groupStages.flatMap({ $0.unsortedTeams() }).flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) + if let startDate = tournament.groupStageStartDate() { + Section { + CallView(teams: tournament.groupStageTeams(), callDate: startDate, matchFormat: tournament.groupStageMatchFormat, roundLabel: "poule") + } header: { + Text("Convoquer toutes les équipes de poules à la même heure") + } + } + _sameTimeGroupStageView(groupStages: groupStages) ForEach(groupStages) { groupStage in diff --git a/PadelClub/Views/Cashier/Event/EventStatusView.swift b/PadelClub/Views/Cashier/Event/EventStatusView.swift index 2cf1680..5cf37ff 100644 --- a/PadelClub/Views/Cashier/Event/EventStatusView.swift +++ b/PadelClub/Views/Cashier/Event/EventStatusView.swift @@ -7,17 +7,28 @@ import SwiftUI import PadelClubData +import LeStorage struct EventStatusView: View { @State private var teamsCount: Int? - + @State private var includeWaitingList: Bool = false + @EnvironmentObject var networkMonitor: NetworkMonitor + + @State private var contactType: ContactType? = nil + @State private var contactMethod: Int = 1 + @State private var contactRecipients: Set = Set() + @State private var sentError: ContactManagerError? = nil + let tournaments: [Tournament] + var event: Event? + init(tournament: Tournament) { self.tournaments = [tournament] } init(event: Event) { + self.event = event self.tournaments = event.confirmedTournaments() } @@ -97,11 +108,118 @@ struct EventStatusView: View { } } } + + if event != nil { + _contactAllButtonView() + } } .task { await self._calculateTeamsCount() } .toolbarBackground(.visible, for: .navigationBar) .navigationBarTitleDisplayMode(.inline) + .alert("Un problème est survenu", isPresented: messageSentFailed) { + Button("OK") { + } + } message: { + Text(_networkErrorMessage) + } + .sheet(item: $contactType) { contactType in + Group { + switch contactType { + case .message(_, let recipients, let body, _): + MessageComposeView(recipients: recipients, body: body) { result in + switch result { + case .cancelled: + break + case .failed: + self.sentError = .messageFailed + case .sent: + if networkMonitor.connected == false { + self.contactType = nil + self.sentError = .messageNotSent + } + @unknown default: + break + } + } + case .mail(_, let recipients, let bccRecipients, let body, let subject, _): + MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in + switch result { + case .cancelled, .saved: + self.contactType = nil + case .failed: + self.contactType = nil + self.sentError = .mailFailed + case .sent: + if networkMonitor.connected == false { + self.contactType = nil + self.sentError = .mailNotSent + } + @unknown default: + break + } + } + } + } + .tint(.master) + } } + + private func _contactAllButtonView() -> some View { + Section { + Toggle("Inclure les équipes en liste d'attente", isOn: $includeWaitingList) + + RowButtonView("Contacter toutes les équipes", systemImage: "paperplane") { + + var teams = [TeamRegistration]() + + if includeWaitingList { + teams = tournaments.flatMap { $0.allTeamsWithoutWalkOut() } + } else { + teams = tournaments.flatMap { $0.selectedSortedTeams() } + } + + _contact(teams: teams) + } + } footer: { + Text("Permet de rédiger un mail à tous les équipes de tous les tournois.") + } + .disabled(StoreCenter.main.isAuthenticated == false) + } + + func finalMessage() -> String? { + event?.eventLinksPasteData() + } + + func subjectMessage() -> String? { + event?.eventTitle() + } + + var messageSentFailed: Binding { + Binding { + sentError != nil + } set: { newValue in + if newValue == false { + sentError = nil + } + } + } + + func umpiresEmail() -> [String] { + let mails = tournaments.compactMap({ + $0.umpireMail() + }) + + return mails.flatMap({ $0 }) + } + + fileprivate func _contact(teams: [TeamRegistration]) { + contactType = .mail(date: nil, recipients: umpiresEmail(), bccRecipients: teams.flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: subjectMessage(), tournamentBuild: nil) + } + + private var _networkErrorMessage: String { + ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected) + } + } diff --git a/PadelClub/Views/Components/StepperView.swift b/PadelClub/Views/Components/StepperView.swift index 0c8acab..5319f92 100644 --- a/PadelClub/Views/Components/StepperView.swift +++ b/PadelClub/Views/Components/StepperView.swift @@ -95,7 +95,7 @@ struct StepperView: View { } fileprivate func _plusIsDisabled() -> Bool { - count >= (maximum ?? 90_000) + count >= (maximum ?? 90_415) } fileprivate func _add() { diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index ba8d0ee..ef9a947 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -160,7 +160,7 @@ struct PadelClubView: View { if let maleUnrankedValue = monthData.maleUnrankedValue { Text(maleUnrankedValue.formatted()) } else { - Text(90_000.formatted()) + Text(90_415.formatted()) } } label: { Text("Rang d'un non classé") @@ -179,9 +179,9 @@ struct PadelClubView: View { Text("Dames") } #if DEBUG - RowButtonView("recalc") { - await _calculateLastRank(dataSource: monthData.monthKey) - } + RowButtonView("Recalculer les non classés") { + await _calculateLastRank(dataSource: monthData.monthKey) + } #endif } header: { HStack { diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 7bc6597..8199432 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -138,9 +138,17 @@ struct PlayerDetailView: View { } } - if player.isMalePlayer() == false && tournament.tournamentCategory == .men, let rank = player.rank { + let maxMaleUnrankedValue: Int = tournament.maleUnrankedValue ?? 90_415 + + if player.isMalePlayer() == false && tournament.tournamentCategory == .men && (player.rank == maxMaleUnrankedValue || player.rank == nil) { + Section { + Text("Une joueuse non classée dans un tournoi messieurs aura le rang d'un joueur non classé.") + } header: { + Text("Ré-assimilation") + } + } else if player.isMalePlayer() == false && tournament.tournamentCategory == .men, let rank = player.rank { Section { - let value = PlayerRegistration.addon(for: rank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) + let value = PlayerRegistration.addon(for: rank, manMax: maxMaleUnrankedValue, womanMax: tournament.femaleUnrankedValue ?? 0) LabeledContent { Text(value.formatted()) } label: { diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 4efa7eb..d239148 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -30,7 +30,7 @@ struct EditingTeamView: View { @State private var refundMessage: String? @State private var registrationDateModified: Date @State private var uniqueRandomIndex: Int - + @State private var isDeleting: Bool = false var messageSentFailed: Binding { Binding { @@ -44,6 +44,10 @@ struct EditingTeamView: View { var hasChanged: Binding { Binding { + if isDeleting { + return false + } + if canSaveWithoutWarning() { return false } @@ -286,6 +290,7 @@ struct EditingTeamView: View { Section { RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") { + isDeleting = true _resetTeam() team.deleteTeamScores() do { diff --git a/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift b/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift index 594a3da..d26cddc 100644 --- a/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift +++ b/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift @@ -259,7 +259,7 @@ struct ConsolationTournamentImportView: View { return playerCopy } - let teamHolder = FileImportManager.TeamHolder.init(players: players, tournamentCategory: tournament.category, tournamentAgeCategory: tournament.federalAgeCategory, previousTeam: tournament.findTeam(players), registrationDate: Date(), name: team.name, tournament: tournament) + let teamHolder = FileImportManager.TeamHolder.init(players: players, tournamentCategory: tournament.category, tournamentAgeCategory: tournament.federalAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), registrationDate: Date(), name: team.name, tournament: tournament) return teamHolder } diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 7e71726..70b82c2 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -116,7 +116,7 @@ struct FileImportView: View { if tournament.isAnimation() { return teams.sorted(by: \.weight) } - return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight) + return teams.filter { $0.tournamentLevel == tournament.tournamentLevel && $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight) } private func _deleteTeams(teams: [TeamRegistration]) async { @@ -174,7 +174,7 @@ struct FileImportView: View { RowButtonView("Démarrer l'importation") { if let fileContent { do { - try await _startImport(fileContent: fileContent) + try await _startImport(fileContent: fileContent, allTournaments: false) } catch { errorMessage = error.localizedDescription } @@ -198,7 +198,7 @@ struct FileImportView: View { multiImport = true if let fileContent { do { - try await _startImport(fileContent: fileContent) + try await _startImport(fileContent: fileContent, allTournaments: true) } catch { errorMessage = error.localizedDescription } @@ -286,7 +286,7 @@ struct FileImportView: View { Task { if let fileContent { do { - try await _startImport(fileContent: fileContent) + try await _startImport(fileContent: fileContent, allTournaments: false) } catch { errorMessage = error.localizedDescription } @@ -305,7 +305,7 @@ struct FileImportView: View { Task { if let fileContent { do { - try await _startImport(fileContent: fileContent) + try await _startImport(fileContent: fileContent, allTournaments: false) } catch { errorMessage = error.localizedDescription } @@ -353,14 +353,20 @@ struct FileImportView: View { } } - let unfound = _getUnfound(tournament: tournament, fromTeams: _filteredTeams) + let unfound = _getUnfound(tournament: tournament, fromTeams: _filteredTeams).sorted(by: \.weight) if unfound.isEmpty == false { Section { - LabeledContent { - Text(unfound.count.formatted()) + DisclosureGroup { + ForEach(unfound) { + TeamRowView(team: $0) + } } label: { - Text("Équipe\(unfound.count.pluralSuffix) précédente\(unfound.count.pluralSuffix) introuvable\(unfound.count.pluralSuffix)") + LabeledContent { + Text(unfound.count.formatted()) + } label: { + Text("Équipe\(unfound.count.pluralSuffix) précédente\(unfound.count.pluralSuffix) introuvable\(unfound.count.pluralSuffix)") + } } } footer: { Text("Équipes de votre liste précédente non détectées dans ce nouveau fichier") @@ -429,6 +435,7 @@ struct FileImportView: View { } selectedFile.stopAccessingSecurityScopedResource() } catch { + Logger.error(error) errorMessage = error.localizedDescription } } @@ -437,6 +444,7 @@ struct FileImportView: View { } } case .failure(let error): + Logger.error(error) errorMessage = error.localizedDescription } }) @@ -520,6 +528,7 @@ struct FileImportView: View { struct CombinedCategory: Identifiable, Hashable { + let tournamentLevel: TournamentLevel let tournamentCategory: TournamentCategory let federalTournamentAge: FederalTournamentAge @@ -528,7 +537,7 @@ struct FileImportView: View { } } - private func _startImport(fileContent: String) async throws { + private func _startImport(fileContent: String, allTournaments: Bool) async throws { await MainActor.run { errorMessage = nil teams.removeAll() @@ -538,10 +547,10 @@ struct FileImportView: View { } let event: Event? = tournament.eventObject() - if let event, event.federalTournaments().count > 1 { + if let event, event.federalTournaments().count > 1, allTournaments == true { var categoriesDone: [CombinedCategory] = [] for someTournament in event.federalTournaments() { - let combinedCategory = CombinedCategory(tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge) + let combinedCategory = CombinedCategory(tournamentLevel: someTournament.tournamentLevel, tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge) if categoriesDone.contains(combinedCategory) == false { let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: someTournament, fileProvider: fileProvider, checkingCategoryDisabled: false, chunkByParameter: chunkByParameter) self.teams += _teams