diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 1cd11de..5a809bf 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -912,6 +912,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 */; }; @@ -1354,6 +1357,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 = ""; }; @@ -1950,6 +1954,7 @@ isa = PBXGroup; children = ( FF6EC8FD2B94792300EA7F5A /* Screen.swift */, + FFB39B332D8E8B05008E0C89 /* MatchSpot.swift */, FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */, FF7091652B90F0B000AB08DA /* TabDestination.swift */, FF025AEC2BD1513700A86CF8 /* AppScreen.swift */, @@ -2777,6 +2782,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 */, @@ -3092,6 +3098,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 */, @@ -3384,6 +3391,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 */, @@ -3650,7 +3658,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.25; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3695,7 +3703,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.25; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3902,7 +3910,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.23; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3944,7 +3952,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.23; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 554d8fd..5149a60 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -1018,7 +1018,11 @@ defer { func ancestors() -> [Match] { previousMatches() + loserMatches() } - + + func matchSpots() -> [MatchSpot] { + [MatchSpot(match: self, teamPosition: .one), MatchSpot(match: self, teamPosition: .two)] + } + func insertOnServer() { self.tournamentStore?.matches.writeChangeAndInsertOnServer(instance: self) for teamScore in self.teamScores { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index b529584..cd458bf 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -393,18 +393,32 @@ defer { func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { 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 { case 0...1: @@ -453,28 +467,32 @@ 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 targetSpots = availableSeedSpot.isEmpty ? availableSeedOpponentSpot.count : availableSeedSpot.count if availableSeedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { - print("availableSeedGroup == SeedInterval(first: 3, last: 4)") return availableSeedGroup } - 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) + } else if fullLeftSeeds.count > 1, targetSpots > 1, fullLeftSeeds.count >= targetSpots { + let seeds = seeds() + if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) { + return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: SeedInterval(first: firstIndex + 1, last: firstIndex + targetSpots)) + } } } } @@ -507,6 +525,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() @@ -1105,7 +1131,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) @@ -1115,6 +1151,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 { @@ -2563,8 +2600,8 @@ extension Tournament { 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/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/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 03652a8..eddbe65 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/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/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 29c47a8..8170ae0 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 364aa90..1b75e2e 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -109,7 +109,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 { @@ -136,14 +136,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 @@ -155,7 +155,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") } } @@ -170,15 +170,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 @@ -190,7 +190,7 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Activer l'inscription en ligne") + Text("Activer") } } @@ -203,7 +203,7 @@ struct EventListView: View { } } } label: { - Text("Rafraîchir la liste des équipes inscrites en ligne") + Text("M-à-j des inscriptions") } @@ -217,13 +217,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] { @@ -263,8 +314,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/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index a9b8865..1b16842 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -104,9 +104,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 c2c76bd..1371a9e 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -131,34 +131,7 @@ 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 availableQualifiedTeams.isEmpty == false { @@ -312,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 { @@ -403,32 +382,51 @@ 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 { + let warnGroupWillCloseRound = _warnGroupWillCloseRound(availableSeedGroup: availableSeedGroup) + Section { + 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) + } + } +// 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.") + } + } -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", 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 { +// 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.") + } + } + } } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 7759ef5..be0dff0 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") { 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") + } + } } }