From 377f1eced46318ccf172aa1a590fe4e256351ca5 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 22 Mar 2025 10:17:30 +0100 Subject: [PATCH 1/5] add special position management --- PadelClub.xcodeproj/project.pbxproj | 8 +++ PadelClub/Data/Match.swift | 3 + PadelClub/Data/Tournament.swift | 23 +++++-- PadelClub/ViewModel/MatchSpot.swift | 29 +++++++++ PadelClub/Views/Round/RoundView.swift | 90 +++++++++++---------------- 5 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 PadelClub/ViewModel/MatchSpot.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 1dd8dcb..f470fec 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -801,6 +801,9 @@ FFB378342D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; }; FFB378352D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; }; FFB378362D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; }; + FFB39B342D8E8B05008E0C89 /* MatchSpot.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */; }; + FFB39B352D8E8B05008E0C89 /* MatchSpot.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */; }; + FFB39B362D8E8B05008E0C89 /* MatchSpot.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */; }; FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; FFBA2D2D2CA2CE9E00D5BBDD /* CodingContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C33F752C9B1EC5006316DE /* CodingContainer+Extensions.swift */; }; @@ -1205,6 +1208,7 @@ FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = ""; }; FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatGuideView.swift; sourceTree = ""; }; + FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchSpot.swift; sourceTree = ""; }; FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = ""; }; FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = ""; }; FFBE62042CE9DA0900815D33 /* MatchViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchViewStyle.swift; sourceTree = ""; }; @@ -1755,6 +1759,7 @@ isa = PBXGroup; children = ( FF6EC8FD2B94792300EA7F5A /* Screen.swift */, + FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */, FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */, FF7091652B90F0B000AB08DA /* TabDestination.swift */, FF025AEC2BD1513700A86CF8 /* AppScreen.swift */, @@ -2517,6 +2522,7 @@ FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, + FFB39B352D8E8B05008E0C89 /* MatchSpot.swift in Sources */, FF17CA4A2CB915A1003C7323 /* MultiCourtPickerView.swift in Sources */, FF9268072BCE94D90080F940 /* TournamentCallView.swift in Sources */, FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, @@ -2809,6 +2815,7 @@ FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */, FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */, FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */, + FFB39B342D8E8B05008E0C89 /* MatchSpot.swift in Sources */, FF17CA4B2CB915A1003C7323 /* MultiCourtPickerView.swift in Sources */, FF4CBFEC2C996C0600151637 /* TournamentCallView.swift in Sources */, FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */, @@ -3080,6 +3087,7 @@ FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */, FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */, FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */, + FFB39B362D8E8B05008E0C89 /* MatchSpot.swift in Sources */, FF17CA492CB915A1003C7323 /* MultiCourtPickerView.swift in Sources */, FF70FB6B2C90584900129CC2 /* TournamentCallView.swift in Sources */, FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */, diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 5afa194..f08ef76 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -1078,6 +1078,9 @@ defer { previousMatches() + loserMatches() } + func matchSpots() -> [MatchSpot] { + [MatchSpot(match: self, teamPosition: .one), MatchSpot(match: self, teamPosition: .two)] + } enum CodingKeys: String, CodingKey { case _id = "id" diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 6f18e49..a7769eb 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -761,19 +761,19 @@ defer { } func seedGroupAvailable(atRoundIndex roundIndex: Int, availableSeedGroup: SeedInterval) -> SeedInterval? { - - if availableSeeds().isEmpty == false && roundIndex >= lastSeedRound() { + let fullLeftSeeds = availableSeeds() + if fullLeftSeeds.isEmpty == false && roundIndex >= lastSeedRound() { if availableSeedGroup == SeedInterval(first: 1, last: 2) { return availableSeedGroup } let availableSeeds = seeds(inSeedGroup: availableSeedGroup) let availableSeedSpot = availableSeedSpot(inRoundIndex: roundIndex) let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex) - + let maxSpots = max(availableSeedSpot.count, availableSeedOpponentSpot.count) if availableSeedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { - print("availableSeedGroup == SeedInterval(first: 3, last: 4)") return availableSeedGroup + } else if availableSeedGroup == SeedInterval(first: 5, last: 8), maxSpots == 6, availableSeeds.count == 2 { + return SeedInterval(first: 7, last: 12) } - if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count { return availableSeedGroup } else if availableSeeds.count == availableSeedOpponentSpot.count && availableSeedGroup.count == availableSeedOpponentSpot.count { @@ -784,6 +784,11 @@ defer { }) { return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: chunk) } + } else if fullLeftSeeds.count % maxSpots == 0 { + let seeds = seeds() + if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { + return SeedInterval(first: firstIndex + 1, last: firstIndex + maxSpots) + } } } @@ -815,6 +820,14 @@ defer { for (index, seed) in availableSeeds.enumerated() { seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) } + } else if seedGroup == SeedInterval(first: 5, last: 6), availableSeedSpot.count == 4 { + var spots = [Match]() + spots.append(availableSeedSpot[1]) + spots.append(availableSeedSpot[2]) + spots = spots.shuffled() + for (index, seed) in availableSeeds.enumerated() { + seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) + } } else { if availableSeeds.count <= availableSeedSpot.count { let spots = availableSeedSpot.shuffled() diff --git a/PadelClub/ViewModel/MatchSpot.swift b/PadelClub/ViewModel/MatchSpot.swift new file mode 100644 index 0000000..61df6c7 --- /dev/null +++ b/PadelClub/ViewModel/MatchSpot.swift @@ -0,0 +1,29 @@ +// +// MatchSpot.swift +// PadelClub +// +// Created by razmig on 22/03/2025. +// + + +struct MatchSpot: SpinDrawable { + let match: Match + let teamPosition: TeamPosition + + func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] { + [match.roundTitle(), matchTitle(displayStyle: displayStyle)].compactMap { $0 } + } + + func matchTitle(displayStyle: DisplayStyle) -> String { + [match.matchTitle(displayStyle), teamPositionLabel()].joined(separator: " - ") + } + + func teamPositionLabel() -> String { + switch teamPosition { + case .one: + return "haut" + case .two: + return "bas" + } + } +} diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index e5e0bd2..3ffe945 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -131,33 +131,9 @@ struct RoundView: View { let availableQualifiedTeams = tournament.availableQualifiedTeams() if availableSeeds.isEmpty == false, let availableSeedGroup { - Section { - RowButtonView("Placer \(availableSeedGroup.localizedInterval())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { - Task { - tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) - _save(seeds: availableSeeds) - } - } - } footer: { - if availableSeedGroup.isFixed() == false { - Text("Le tirage au sort ne sera pas visuel. Toutes les équipes de ce chapeau seront tirées.") - } - } - - if (availableSeedGroup.isFixed() == false) { - Section { - Toggle(isOn: $hideNames) { - Text("Masquer les noms") - if hideNames { - Text("Réalise un tirage des positions.") - } - } - RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel") { - self.selectedSeedGroup = availableSeedGroup - } - } footer: { - Text("Le tirage au sort sera visuel et automatique, n'hésitez pas à enregistrer une vidéo de votre écran. Toutes les équipes de ce chapeau seront tirées les unes après les autres.") - } + _seedGroupSection(availableSeeds: availableSeeds, availableSeedGroup: availableSeedGroup) + if upperRound.round.index == 3, availableSeedGroup.first == 5, availableSeedGroup.last == 8, availableSeeds.count > 4, let half = availableSeedGroup.chunks()?.first { + _seedGroupSection(availableSeeds: Array(availableSeeds.prefix(2)), availableSeedGroup: half) } } @@ -319,6 +295,11 @@ struct RoundView: View { array.append(spots[1]) array.append(spots[4]) return array + } else if availableSeedGroup == SeedInterval(first: 5, last: 6), spots.count == 4 { + var array = [Match]() + array.append(spots[1]) + array.append(spots[2]) + return array } else { return spots } @@ -403,32 +384,37 @@ struct RoundView: View { } } } -} - -struct MatchSpot: SpinDrawable { - let match: Match - let teamPosition: TeamPosition - - func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] { - [match.roundTitle(), matchTitle(displayStyle: displayStyle)].compactMap { $0 } - } - func matchTitle(displayStyle: DisplayStyle) -> String { - [match.matchTitle(displayStyle), teamPositionLabel()].joined(separator: " - ") - } - - func teamPositionLabel() -> String { - switch teamPosition { - case .one: - return "haut" - case .two: - return "bas" - } - } -} + private func _seedGroupSection(availableSeeds: [TeamRegistration], availableSeedGroup: SeedInterval) -> some View { + Group { + Section { + RowButtonView("Placer \(availableSeedGroup.localizedInterval())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { + Task { + tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) + _save(seeds: availableSeeds) + } + } + } footer: { + if availableSeedGroup.isFixed() == false { + Text("Le tirage au sort ne sera pas visuel. Toutes les équipes de ce chapeau seront tirées.") + } + } -extension Match { - func matchSpots() -> [MatchSpot] { - [MatchSpot(match: self, teamPosition: .one), MatchSpot(match: self, teamPosition: .two)] + if (availableSeedGroup.isFixed() == false) { + Section { + Toggle(isOn: $hideNames) { + Text("Masquer les noms") + if hideNames { + Text("Réalise un tirage des positions.") + } + } + RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel") { + self.selectedSeedGroup = availableSeedGroup + } + } footer: { + Text("Le tirage au sort sera visuel et automatique, n'hésitez pas à enregistrer une vidéo de votre écran. Toutes les équipes de ce chapeau seront tirées les unes après les autres.") + } + } + } } } From 6ff21edba811a09c2c867e32e000b047377c8cc9 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 23 Mar 2025 07:03:36 +0100 Subject: [PATCH 2/5] fix pdf rules add descritpion sharing system fix slots setup --- PadelClub/Data/Tournament.swift | 49 ++++++++---- PadelClub/Utils/URLs.swift | 7 +- PadelClub/Views/Match/MatchSetupView.swift | 27 +++++++ .../Navigation/Agenda/EventListView.swift | 77 +++++++++++++++---- .../Navigation/Toolbox/ToolboxView.swift | 7 +- PadelClub/Views/Round/RoundView.swift | 21 +++-- 6 files changed, 146 insertions(+), 42 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index a7769eb..ceaa9b3 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -702,16 +702,31 @@ defer { return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] } - func availableSeedGroups() -> [SeedInterval] { + func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] { let seeds = seeds() var availableSeedGroup = Set() for (index, seed) in seeds.enumerated() { if seed.isSeedable(), let seedGroup = seedGroup(for: index) { - availableSeedGroup.insert(seedGroup) + if includeAll { + if let chunks = seedGroup.chunks() { + chunksBy(in: chunks, availableSeedGroup: &availableSeedGroup) + } + } else { + availableSeedGroup.insert(seedGroup) + } } } return availableSeedGroup.sorted(by: <) } + + func chunksBy(in chunks: [SeedInterval], availableSeedGroup: inout Set) { + chunks.forEach { chunk in + availableSeedGroup.insert(chunk) + if let moreChunk = chunk.chunks() { + self.chunksBy(in: moreChunk, availableSeedGroup: &availableSeedGroup) + } + } + } func seedGroup(for alreadySetupSeeds: Int) -> SeedInterval? { switch alreadySetupSeeds { @@ -771,20 +786,24 @@ defer { let maxSpots = max(availableSeedSpot.count, availableSeedOpponentSpot.count) if availableSeedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { return availableSeedGroup - } else if availableSeedGroup == SeedInterval(first: 5, last: 8), maxSpots == 6, availableSeeds.count == 2 { - return SeedInterval(first: 7, last: 12) } if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count { return availableSeedGroup } else if availableSeeds.count == availableSeedOpponentSpot.count && availableSeedGroup.count == availableSeedOpponentSpot.count { return availableSeedGroup } else if let chunks = availableSeedGroup.chunks() { + let seededTeamsCount = self.seededTeams().count if let chunk = chunks.first(where: { seedInterval in - seedInterval.first >= self.seededTeams().count + return seedInterval.first == seededTeamsCount }) { - return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: chunk) + return seedGroupAvailable(atRoundIndex: roundIndex + 1, availableSeedGroup: chunk) + } else { + let seeds = seeds() + if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { + return SeedInterval(first: firstIndex + 1, last: firstIndex + maxSpots) + } } - } else if fullLeftSeeds.count % maxSpots == 0 { + } else if fullLeftSeeds.count % maxSpots == 0 || fullLeftSeeds.count >= maxSpots { let seeds = seeds() if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { return SeedInterval(first: firstIndex + 1, last: firstIndex + maxSpots) @@ -820,14 +839,14 @@ defer { for (index, seed) in availableSeeds.enumerated() { seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) } - } else if seedGroup == SeedInterval(first: 5, last: 6), availableSeedSpot.count == 4 { - var spots = [Match]() - spots.append(availableSeedSpot[1]) - spots.append(availableSeedSpot[2]) - spots = spots.shuffled() - for (index, seed) in availableSeeds.enumerated() { - seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) - } +// } else if seedGroup == SeedInterval(first: 5, last: 6), availableSeedSpot.count == 4 { +// var spots = [Match]() +// spots.append(availableSeedSpot[1]) +// spots.append(availableSeedSpot[2]) +// spots = spots.shuffled() +// for (index, seed) in availableSeeds.enumerated() { +// seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) +// } } else { if availableSeeds.count <= availableSeedSpot.count { let spots = availableSeedSpot.shuffled() diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 2b9e746..452e35d 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -31,9 +31,10 @@ enum URLs: String, Identifiable { case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" //case padelClub = "https://padelclub.app" case tenup = "https://tenup.fft.fr" - case padelCompetitionGeneralGuide = "https://fft-site.cdn.prismic.io/fft-site/Z2mH0ZbqstJ98yso_CHAPITREIRèglesgénérales.pdf" - case padelCompetitionSpecificGuide = "https://fft-site.cdn.prismic.io/fft-site/Z2mHz5bqstJ98ysm_CHAPITREIIICahierdeschargesdestournois.pdf" - case padelRules = "https://xlr.alwaysdata.net/static/rules/padel-rules-2024.pdf" + case padelCompetitionGeneralGuide = "https://padelclub.app/static/rules/padel-guide-general.pdf" + case padelCompetitionSpecificGuide = "https://padelclub.app/static/rules/padel-guide-cdc.pdf" + case padelCompetitionRankingGuide = "https://padelclub.app/static/rules/padel-guide-rankings.pdf" + case padelRules = "https://padelclub.app/static/rules/padel-rules.pdf" case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf" case appReview = "https://apps.apple.com/app/padel-club/id6484163558?mt=8&action=write-review" case appDescription = "https://padelclub.app/download/" diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index dc932bd..72b5b19 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -146,6 +146,33 @@ struct MatchSetupView: View { Label(seedGroup.localizedInterval(), systemImage: "dice") } } + + Divider() + + Menu { + ForEach(tournament.availableSeedGroups(includeAll: true), id: \.self) { seedGroup in + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { + if let randomTeam = tournament.randomSeed(fromSeedGroup: seedGroup) { + randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + do { + try tournamentStore.teamRegistrations.addOrUpdate(instance: randomTeam) + } catch { + Logger.error(error) + } + } + } label: { + Label(seedGroup.localizedInterval(), systemImage: "dice") + } + } + } label: { + Text("plus d'options") + } + } label: { Text("Tirer au sort").tag(nil as SeedInterval?) .underline() diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index f9c0ed5..dc869dc 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -107,7 +107,7 @@ struct EventListView: View { @ViewBuilder private func _options(_ pcTournaments: [Tournament]) -> some View { if let lastDataSource, pcTournaments.anySatisfy({ $0.rankSourceShouldBeRefreshed() != nil && $0.hasEnded() == false }) { - Section { + Menu { Button { Task { do { @@ -134,14 +134,14 @@ struct EventListView: View { } } } label: { - Text("Rafraîchir les classements") + Text("M-à-j des classements") } - } header: { - Text("Source disponible : \(lastDataSource.monthYearFormatted)") + } label: { + Text("Classement \(lastDataSource.monthYearFormatted)") } Divider() } - Section { + Menu { if pcTournaments.anySatisfy({ $0.isPrivate == true }) { Button { pcTournaments.forEach { tournament in @@ -153,7 +153,7 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Afficher ce\(pcTournaments.count.pluralSuffix) tournoi\(pcTournaments.count.pluralSuffix) sur Padel Club") + Text("Afficher sur Padel Club") } } @@ -168,15 +168,15 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Masquer ce\(pcTournaments.count.pluralSuffix) tournoi\(pcTournaments.count.pluralSuffix) sur Padel Club") + Text("Masquer sur Padel Club") } } - } header: { + } label: { Text("Visibilité sur Padel Club") } Divider() if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) || pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true && $0.hasEnded() == false }) { - Section { + Menu { if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) { Button { pcTournaments.forEach { tournament in @@ -188,7 +188,7 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Activer l'inscription en ligne") + Text("Activer") } } @@ -201,7 +201,7 @@ struct EventListView: View { } } } label: { - Text("Rafraîchir la liste des équipes inscrites en ligne") + Text("M-à-j des inscriptions") } @@ -215,13 +215,64 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Désactiver l'inscription en ligne") + Text("Désactiver") } } - } header: { + } label: { Text("Inscription en ligne") } } + Divider() + Menu { + Button { + pcTournaments.forEach { tournament in + tournament.information = nil + } + do { + try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) + } catch { + Logger.error(error) + } + } label: { + Text("Effacer les descriptions") + } + + let info = Set(pcTournaments.compactMap { tournament in + tournament.information?.trimmedMultiline + }).joined(separator: "\n") + + if info.isEmpty == false { + Button { + pcTournaments.forEach { tournament in + tournament.information = info + } + do { + try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) + } catch { + Logger.error(error) + } + + } label: { + Text("Mettre '\(info.trunc(length: 12))'") + } + } + + PasteButton(payloadType: String.self) { strings in + if let pasteboard = strings.first { + pcTournaments.forEach { tournament in + tournament.information = pasteboard + } + do { + try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) + } catch { + Logger.error(error) + } + } + } + } label: { + Text("Description des tournois") + } + } private func _nextMonths() -> [Date] { diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index 487f80c..c6b2177 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -170,9 +170,10 @@ struct ToolboxView: View { } Section { - Link("Accéder au guide de la compétition", destination: URLs.padelCompetitionGeneralGuide.url) - Link("Accéder aux CDC des tournois", destination: URLs.padelCompetitionSpecificGuide.url) - Link("Accéder aux règles du jeu", destination: URLs.padelRules.url) + Link("Guide de la compétition", destination: URLs.padelCompetitionGeneralGuide.url) + Link("CDC des tournois", destination: URLs.padelCompetitionSpecificGuide.url) + Link("Barèmes et assimilations", destination: URLs.padelCompetitionRankingGuide.url) + Link("Règles du jeu", destination: URLs.padelRules.url) Link("Décharge des temps de repos", destination: URLs.restingDischarge.url) } header: { Text("Documents fédéraux") diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 3ffe945..343c330 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -132,9 +132,6 @@ struct RoundView: View { if availableSeeds.isEmpty == false, let availableSeedGroup { _seedGroupSection(availableSeeds: availableSeeds, availableSeedGroup: availableSeedGroup) - if upperRound.round.index == 3, availableSeedGroup.first == 5, availableSeedGroup.last == 8, availableSeeds.count > 4, let half = availableSeedGroup.chunks()?.first { - _seedGroupSection(availableSeeds: Array(availableSeeds.prefix(2)), availableSeedGroup: half) - } } if availableQualifiedTeams.isEmpty == false { @@ -295,11 +292,6 @@ struct RoundView: View { array.append(spots[1]) array.append(spots[4]) return array - } else if availableSeedGroup == SeedInterval(first: 5, last: 6), spots.count == 4 { - var array = [Match]() - array.append(spots[1]) - array.append(spots[2]) - return array } else { return spots } @@ -394,6 +386,14 @@ struct RoundView: View { _save(seeds: availableSeeds) } } +// if upperRound.round.index == 3, availableSeedGroup.first == 5, availableSeedGroup.last == 8, availableSeeds.count > 1, let half = availableSeedGroup.chunks()?.first { +// FooterButtonView("ou placer \(half.localizedInterval())" + ((half.isFixed() == false) ? " au hasard" : "")) { +// Task { +// tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: half) +// _save(seeds: availableSeeds) +// } +// } +// } } footer: { if availableSeedGroup.isFixed() == false { Text("Le tirage au sort ne sera pas visuel. Toutes les équipes de ce chapeau seront tirées.") @@ -411,6 +411,11 @@ struct RoundView: View { RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel") { self.selectedSeedGroup = availableSeedGroup } +// if upperRound.round.index == 3, availableSeedGroup.first == 5, availableSeedGroup.last == 8, availableSeeds.count > 1, let half = availableSeedGroup.chunks()?.first { +// FooterButtonView("ou tirage au sort \(half.localizedInterval()) visuel") { +// self.selectedSeedGroup = half +// } +// } } footer: { Text("Le tirage au sort sera visuel et automatique, n'hésitez pas à enregistrer une vidéo de votre écran. Toutes les équipes de ce chapeau seront tirées les unes après les autres.") } From ebb4d9a3673e4559414519116c91fa39dfab0df8 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 23 Mar 2025 07:03:53 +0100 Subject: [PATCH 3/5] v1.1.26 --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index f470fec..ed6fa3c 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3352,7 +3352,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.25; + MARKETING_VERSION = 1.1.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3397,7 +3397,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.25; + MARKETING_VERSION = 1.1.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 8372030d83af3e4703a35af03022ee7b2fbc18d3 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 23 Mar 2025 08:11:16 +0100 Subject: [PATCH 4/5] fix stuff for setuping heads --- PadelClub/Data/Tournament.swift | 13 ++++--------- PadelClub/PadelClubApp.swift | 2 +- PadelClub/Views/Round/RoundView.swift | 11 +++++++++-- .../Views/Tournament/TournamentBuildView.swift | 10 ++++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index ceaa9b3..48092f9 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -783,7 +783,7 @@ defer { let availableSeeds = seeds(inSeedGroup: availableSeedGroup) let availableSeedSpot = availableSeedSpot(inRoundIndex: roundIndex) let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex) - let maxSpots = max(availableSeedSpot.count, availableSeedOpponentSpot.count) + let targetSpots = availableSeedSpot.isEmpty ? availableSeedOpponentSpot.count : availableSeedSpot.count if availableSeedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { return availableSeedGroup } @@ -796,18 +796,13 @@ defer { if let chunk = chunks.first(where: { seedInterval in return seedInterval.first == seededTeamsCount }) { - return seedGroupAvailable(atRoundIndex: roundIndex + 1, availableSeedGroup: chunk) - } else { + return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: chunk) + } else if fullLeftSeeds.count > 1, targetSpots > 1, fullLeftSeeds.count >= targetSpots { let seeds = seeds() if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { - return SeedInterval(first: firstIndex + 1, last: firstIndex + maxSpots) + return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: SeedInterval(first: firstIndex + 1, last: firstIndex + targetSpots)) } } - } else if fullLeftSeeds.count % maxSpots == 0 || fullLeftSeeds.count >= maxSpots { - let seeds = seeds() - if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { - return SeedInterval(first: firstIndex + 1, last: firstIndex + maxSpots) - } } } diff --git a/PadelClub/PadelClubApp.swift b/PadelClub/PadelClubApp.swift index 35e88fd..aed4326 100644 --- a/PadelClub/PadelClubApp.swift +++ b/PadelClub/PadelClubApp.swift @@ -106,7 +106,7 @@ struct PadelClubApp: App { } .task { - try? Tips.resetDatastore() +// try? Tips.resetDatastore() try? Tips.configure([ .displayFrequency(.immediate), diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 343c330..3e94096 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -285,6 +285,12 @@ struct RoundView: View { seedSpaceLeft.isEmpty ? true : false } + private func _warnGroupWillCloseRound(availableSeedGroup: SeedInterval) -> Bool { + opposingSeeding + && seedSpaceLeft.count <= availableSeedGroup.count + && tournament.rounds().count - 1 > upperRound.round.index + } + private func _availableSeedSpot(availableSeedGroup: SeedInterval) -> [Match] { let spots = opposingSeeding ? spaceLeft : seedSpaceLeft if availableSeedGroup == SeedInterval(first: 3, last: 4), spots.count == 6 { @@ -379,8 +385,9 @@ struct RoundView: View { private func _seedGroupSection(availableSeeds: [TeamRegistration], availableSeedGroup: SeedInterval) -> some View { Group { + let warnGroupWillCloseRound = _warnGroupWillCloseRound(availableSeedGroup: availableSeedGroup) Section { - RowButtonView("Placer \(availableSeedGroup.localizedInterval())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { + RowButtonView("Placer \(availableSeedGroup.localizedInterval())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : ""), role: warnGroupWillCloseRound ? .destructive : nil, confirmationMessage: warnGroupWillCloseRound ? "Attention, tous vos matchs du tour précédent seront désactivés" : nil) { Task { tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) _save(seeds: availableSeeds) @@ -408,7 +415,7 @@ struct RoundView: View { Text("Réalise un tirage des positions.") } } - RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel") { + RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel", role: warnGroupWillCloseRound ? .destructive : nil, confirmationMessage: warnGroupWillCloseRound ? "Attention, tous vos matchs du tour précédent seront désactivés" : nil) { self.selectedSeedGroup = availableSeedGroup } // if upperRound.round.index == 3, availableSeedGroup.first == 5, availableSeedGroup.last == 8, availableSeeds.count > 1, let half = availableSeedGroup.chunks()?.first { diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 62b72c9..35c1a2c 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -130,10 +130,6 @@ struct TournamentBuildView: View { } } } - } else { - NavigationLink(value: Screen.restingTime) { - Text("Temps de repos") - } } if state == .running || state == .finished { TournamentInscriptionView(tournament: tournament) @@ -203,6 +199,12 @@ struct TournamentBuildView: View { cashierStatus = await tournament.cashierStatus() } } + + if state == .running { + NavigationLink(value: Screen.restingTime) { + Text("Temps de repos") + } + } } } From b27cdfa1e62764eacfd7fa3a2020d4f756e28769 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 24 Mar 2025 11:18:36 +0100 Subject: [PATCH 5/5] fix event list team loading prepare feature --- PadelClub/Data/Tournament.swift | 15 +++++++++++++-- .../Views/Navigation/Agenda/EventListView.swift | 6 ++++-- .../Tournament/Screen/TournamentRankView.swift | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 48092f9..fe0ef7b 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1439,7 +1439,17 @@ defer { } let groupStages = groupStages() - let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + var baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + + //TODO: RAZ ajouté une option pour choisir entre la règle officiel et la règle 'maison' + /* + Request by Philippe Morin 24/03/2025 + */ + let defaultOption = false + + if defaultOption { + baseRank += qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - 1 + } let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 })) groupStages.forEach { groupStage in let groupStageTeams = groupStage.teams(true) @@ -1449,6 +1459,7 @@ defer { let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0) let _index = baseRank + groupStageWidth + 1 - (index > qualifiedPerGroupStage ? groupStageAdditionalQualified : 0) + print("finalRanking", team.teamLabel() , _index, baseRank, groupStageWidth) if let existingTeams = teams[_index] { teams[_index] = existingTeams + [team.id] } else { @@ -2951,7 +2962,7 @@ extension Tournament { func deadline(for type: TournamentDeadlineType) -> Date? { guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil } - var daysOffset = type.daysOffset(level: tournamentLevel) + let daysOffset = type.daysOffset(level: tournamentLevel) if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) { let startOfDay = Calendar.current.startOfDay(for: date) return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay) diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index dc869dc..adc4c01 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -312,8 +312,10 @@ struct EventListView: View { TournamentCellView(tournament: tournament) .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name.CollectionDidLoad), perform: { notification in - if let store = notification.object as? StoredCollection, store.id == tournament.id { - tournament.lastTeamRefresh = Date() + if let store = notification.object as? StoredCollection { + if store.storeId == tournament.id { + tournament.lastTeamRefresh = Date() + } } }) diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index b3214ca..17c0061 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -175,7 +175,7 @@ struct TournamentRankView: View { @State var key: Int var body: some View { - VStack(spacing: 0) { + VStack { if editMode?.wrappedValue.isEditing == true { if key > 1 { FooterButtonView("monter") {