diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index e9ff2e1..8de65b6 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-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 */; }; + 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 */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; @@ -460,6 +461,7 @@ FF1F4B7E2BFA0105000B4573 /* player-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "player-template.html"; sourceTree = ""; }; FF1F4B7F2BFA0105000B4573 /* tournament-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "tournament-template.html"; sourceTree = ""; }; FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = ""; }; + FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = ""; }; FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; @@ -1209,6 +1211,7 @@ isa = PBXGroup; children = ( FFBF41812BF73EB3001B24CB /* EventView.swift */, + FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */, FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */, FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */, FF8F263A2BAD528600650388 /* EventCreationView.swift */, @@ -1550,6 +1553,7 @@ FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, + FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */, FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */, FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index c8f5463..78dc605 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -50,7 +50,7 @@ class GroupStage: ModelObject, Storable { } func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { - if let name { return "Poule " + name } + if let name { return name } switch displayStyle { case .wide: return "Poule \(index + 1)" diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index f910364..db7f41b 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -74,8 +74,8 @@ class PlayerRegistration: ModelObject, Storable { } internal init(federalData: [String], sex: Int, sexUnknown: Bool) { - lastName = federalData[0].trimmed.capitalized - firstName = federalData[1].trimmed.uppercased() + lastName = federalData[0].trimmed.uppercased() + firstName = federalData[1].trimmed.capitalized birthdate = federalData[2] licenceId = federalData[3] clubName = federalData[4] diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index edf0ebd..eaf6cad 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -228,8 +228,10 @@ class TeamRegistration: ModelObject, Storable { func updatePlayers(_ players: Set, inTournamentCategory tournamentCategory: TournamentCategory) { + let previousPlayers = Set(unsortedPlayers()) + let playersToRemove = previousPlayers.subtracting(players) do { - try DataStore.shared.playerRegistrations.delete(contentOfs: unsortedPlayers()) + try DataStore.shared.playerRegistrations.delete(contentOfs: playersToRemove) } catch { Logger.error(error) } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index db7d353..aa1acfc 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -404,14 +404,9 @@ class Tournament : ModelObject, Storable { return false } } - - func shareURL() -> URL? { - return URLs.main.url.appending(path: "tournament/\(id)") - } - - func broadcastURL() -> URL? { - return URLs.main.url.appending(path: "tournament/\(id)/broadcast") + func shareURL(_ pageLink: PageLink = .matches) -> URL? { + return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path) } func courtUsed() -> [Int] { diff --git a/PadelClub/HTML Templates/tournament-template.html b/PadelClub/HTML Templates/tournament-template.html index 9bcb606..1d6f1b9 100644 --- a/PadelClub/HTML Templates/tournament-template.html +++ b/PadelClub/HTML Templates/tournament-template.html @@ -80,13 +80,13 @@ } .player { - font-size:28px; + font-size:26px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .hiddenPlayer { - font-size:28px; + font-size:26px; white-space: pre; overflow: hidden; text-overflow: ellipsis; diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index c8e8d79..d9a068d 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -103,6 +103,7 @@ class HtmlGenerator: ObservableObject { groupStageDone = 0 groupStageIsReady = false pdfDocument = PDFDocument() + rects.removeAll() try? FileManager.default.removeItem(at: pdfURL!) print("buildPDF", width, height, zoomLevel ?? 0) if let zoomLevel { diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index 74de02b..ea5a64b 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -66,7 +66,7 @@ enum HtmlService { } else { template = template.replacingOccurrences(of: "{{bracketStartDate}}", with: "") } - template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle()) + template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle(.short)) template = template.replacingOccurrences(of: "{{bracketTitle}}", with: bracket.groupStageTitle()) var col = "" @@ -133,7 +133,7 @@ enum HtmlService { case .player(let entrant): var template = html if let playerOne = entrant.players()[safe: 0] { - template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel(.short)) + template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel()) if withRank { template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))") } else { @@ -142,7 +142,7 @@ enum HtmlService { } if let playerTwo = entrant.players()[safe: 1] { - template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel(.short)) + template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel()) if withRank { template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))") } else { @@ -192,7 +192,7 @@ enum HtmlService { return bracket case .template(let tournament): var template = html - template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle()) + template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle(.short)) var brackets = "" for round in tournament.rounds() { brackets = brackets.appending(HtmlService.bracket(tournament: tournament, roundIndex: round.index).html(headName: headName, withRank: withRank, withScore: withScore)) diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 194bb8a..e437869 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -21,3 +21,36 @@ enum URLs: String, Identifiable { } } + +enum PageLink: String, Identifiable, CaseIterable { + case teams = "Équipes" + case summons = "Convocations" + case groupStages = "Poules" + case matches = "Matchs" + case rankings = "Classement" + case broadcast = "Broadcast" + + var id: String { self.rawValue } + + func localizedLabel() -> String { + rawValue + } + + var path: String { + switch self { + case .matches: + return "" + case .teams: + return "teams" + case .summons: + return "summons" + case .rankings: + return "rankings" + case .groupStages: + return "group-stages" + case .broadcast: + return "broadcast" + } + } +} + diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 38f1704..ec02e47 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -9,6 +9,8 @@ import SwiftUI import LeStorage struct SendToAllView: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var networkMonitor: NetworkMonitor @@ -18,7 +20,8 @@ struct SendToAllView: View { @State private var sentError: ContactManagerError? = nil let addLink: Bool @State var cannotPayForTournament: Bool = false - + @State private var pageLink: PageLink = .teams + var messageSentFailed: Binding { Binding { sentError != nil @@ -69,12 +72,34 @@ struct SendToAllView: View { } } + if addLink { + Section { + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()) + } + } label: { + Text("Choisir une page du tournoi en particulier") + } + .pickerStyle(.menu) + } + } + Section { RowButtonView("Contacter \(_totalString())") { self._contactAndPay() } } } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + dismiss() + } + + } + } .environment(\.editMode, Binding.constant(EditMode.active)) .headerProminence(.increased) .navigationTitle("Préparation") @@ -141,7 +166,10 @@ struct SendToAllView: View { } func _teams() -> [TeamRegistration] { - _roundTeams() + _groupStagesTeams() + if _roundTeams().isEmpty && _groupStagesTeams().isEmpty { + return tournament.selectedSortedTeams() + } + return _roundTeams() + _groupStagesTeams() } func _roundTeams() -> [TeamRegistration] { @@ -172,11 +200,25 @@ struct SendToAllView: View { } } + func finalMessage() -> String { + var message = [String?]() + message.append("\n\n") + if addLink { + message.append(tournament.shareURL(pageLink)?.absoluteString) + } + + let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature() + + message.append(signature) + + return message.compactMap { $0 }.joined(separator: "\n\n") + } + fileprivate func _contact() { if contactMethod == 0 { - contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: addLink ? tournament.shareURL()?.absoluteString : nil, tournamentBuild: nil) + contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: finalMessage(), tournamentBuild: nil) } else { - contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: addLink ? tournament.shareURL()?.absoluteString : nil, subject: tournament.tournamentTitle(), tournamentBuild: nil) + contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: tournament.tournamentTitle(), tournamentBuild: nil) } } diff --git a/PadelClub/Views/Cashier/Event/EventLinksView.swift b/PadelClub/Views/Cashier/Event/EventLinksView.swift new file mode 100644 index 0000000..1eb1358 --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventLinksView.swift @@ -0,0 +1,60 @@ +// +// EventLinksView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 26/05/2024. +// + +import SwiftUI + +struct EventLinksView: View { + let event: Event + @State private var pageLink: PageLink = .teams + + func eventLinksPasteData() -> String { + var link = [String]() + link.append(event.eventTitle()) + + event.tournaments.forEach({ tournament in + if let url = tournament.shareURL(pageLink) { + var tournamentLink = [String]() + tournamentLink.append(tournament.tournamentTitle()) + tournamentLink.append(url.absoluteString) + link.append(tournamentLink.joined(separator: "\n")) + } + }) + + return link.joined(separator: "\n\n") + } + + + var body: some View { + List { + Section { + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()) + } + } label: { + Text("Choisir une page du tournoi en particulier") + } + .pickerStyle(.menu) + } + + let eventLinksPasteData = eventLinksPasteData() + Section { + Text(eventLinksPasteData) + .italic() + .multilineTextAlignment(.leading) + + + ShareLink("Partagez ce message", item: eventLinksPasteData) + } + } + } +} + +#Preview { + EventLinksView(event: Event.mock()) +} diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index c421f42..294d3df 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -9,6 +9,7 @@ import SwiftUI import LeStorage enum EventDestination: Identifiable, Selectable { + case links case tournaments(Event) case cashier @@ -18,6 +19,8 @@ enum EventDestination: Identifiable, Selectable { func selectionLabel() -> String { switch self { + case .links: + return "Liens" case .tournaments: return "Épreuves" case .cashier: @@ -27,6 +30,8 @@ enum EventDestination: Identifiable, Selectable { func badgeValue() -> Int? { switch self { + case .links: + return nil case .tournaments(let event): return event.tournaments.count case .cashier: @@ -49,7 +54,7 @@ struct EventView: View { @State private var selectedDestination: EventDestination? func allDestinations() -> [EventDestination] { - [.tournaments(event), .cashier] + [.links, .tournaments(event), .cashier] } var body: some View { @@ -60,6 +65,8 @@ struct EventView: View { EventSettingsView(event: event) case .some(let selectedEventDestination): switch selectedEventDestination { + case .links: + EventLinksView(event: event) case .tournaments(let event): EventTournamentsView(event: event) case .cashier: diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 6f175ae..e31a1cc 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -11,7 +11,6 @@ import LeStorage struct GroupStageSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament - @State private var nameAlphabetical: Bool = false @State private var generationDone: Bool = false var body: some View { @@ -60,9 +59,36 @@ struct GroupStageSettingsView: View { Text("Redistribue les équipes par la méthode du serpentin") } - Toggle(isOn: $nameAlphabetical) { - Text("Nommer les poules alphabétiquement") + Section { + RowButtonView("Nommer les poules alphabétiquement", role: .destructive) { + let groupStages = tournament.groupStages() + groupStages.forEach { groupStage in + if let letter = Alphabet.letterForIndex(index: groupStage.index) { + groupStage.name = "Poule " + letter + } + } + do { + try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) + } catch { + Logger.error(error) + } + } } + + Section { + RowButtonView("Supprimer les noms des poules", role: .destructive) { + let groupStages = tournament.groupStages() + groupStages.forEach { groupStage in + groupStage.name = nil + } + do { + try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) + } catch { + Logger.error(error) + } + } + } + } .overlay(alignment: .bottom) { if generationDone { @@ -71,23 +97,6 @@ struct GroupStageSettingsView: View { .deferredRendering(for: .seconds(2)) } } - .onChange(of: nameAlphabetical) { - let groupStages = tournament.groupStages() - if nameAlphabetical { - groupStages.forEach { groupStage in - groupStage.name = Alphabet.letterForIndex(index: groupStage.index) - } - } else { - groupStages.forEach { groupStage in - groupStage.name = nil - } - } - do { - try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) - } catch { - Logger.error(error) - } - } } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 027401c..b07a3b6 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -21,7 +21,8 @@ struct BroadcastView: View { let filter = CIFilter.qrCodeGenerator() @State private var urlToShow: String? @State private var tvMode: Bool = false - + @State private var pageLink: PageLink = .teams + let tournamentPublishingTip = TournamentPublishingTip() let tournamentTVBroadcastTip = TournamentTVBroadcastTip() @@ -180,15 +181,26 @@ struct BroadcastView: View { } } - if let url = tournament.shareURL() { + if let url = tournament.shareURL(pageLink) { LabeledContent { actionForURL(url) } label: { Text("Tournoi") + Text(pageLink.localizedLabel()) + } + } + + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()).tag(pageLink) } + } label: { + Text("Modifier la page du tournoi à partager") } + .pickerStyle(.menu) - if let url = tournament.broadcastURL() { + if let url = tournament.shareURL(.broadcast) { LabeledContent { actionForURL(url) } label: { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift index 3319c81..3796fc7 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -81,7 +81,7 @@ struct TournamentStatusView: View { dismiss() } } footer: { - Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main)")) + Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main.rawValue))")) } } @@ -90,7 +90,7 @@ struct TournamentStatusView: View { Text("Tournoi privé") } } footer: { - Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main)")) + Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))")) } } .toolbarBackground(.visible, for: .navigationBar)