From 83a2b2a5653d38e81fd196f8f47f4b802ed41c6e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 30 Apr 2024 17:25:54 +0200 Subject: [PATCH 1/6] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 6 +- PadelClub/Data/GroupStage.swift | 6 +- PadelClub/Data/Tournament.swift | 2 +- .../{Manager => Utils}/CloudConvert.swift | 0 .../{Manager => Utils}/ContactManager.swift | 0 .../{Manager => Utils}/DisplayContext.swift | 0 .../FileImportManager.swift | 0 .../{Manager => Utils}/LocationManager.swift | 0 .../Network/NetworkFederalService.swift | 0 .../Network/NetworkManager.swift | 0 .../Network/NetworkManagerError.swift | 0 .../{Manager => Utils}/NetworkMonitor.swift | 0 PadelClub/{Manager => Utils}/PadelRule.swift | 0 .../SourceFileManager.swift | 0 .../{Manager => Utils}/SwiftParser.swift | 0 PadelClub/{Manager => Utils}/Tips.swift | 0 PadelClub/{Manager => Utils}/URLs.swift | 0 .../Views/Cashier/CashierDetailView.swift | 2 +- .../GenericDestinationPickerView.swift | 6 +- .../Views/Components/MatchListView.swift | 2 +- PadelClub/Views/Match/MatchDetailView.swift | 128 +++++++++--------- .../TournamentGeneralSettingsView.swift | 23 ++++ .../Views/Tournament/TournamentView.swift | 2 +- 23 files changed, 98 insertions(+), 79 deletions(-) rename PadelClub/{Manager => Utils}/CloudConvert.swift (100%) rename PadelClub/{Manager => Utils}/ContactManager.swift (100%) rename PadelClub/{Manager => Utils}/DisplayContext.swift (100%) rename PadelClub/{Manager => Utils}/FileImportManager.swift (100%) rename PadelClub/{Manager => Utils}/LocationManager.swift (100%) rename PadelClub/{Manager => Utils}/Network/NetworkFederalService.swift (100%) rename PadelClub/{Manager => Utils}/Network/NetworkManager.swift (100%) rename PadelClub/{Manager => Utils}/Network/NetworkManagerError.swift (100%) rename PadelClub/{Manager => Utils}/NetworkMonitor.swift (100%) rename PadelClub/{Manager => Utils}/PadelRule.swift (100%) rename PadelClub/{Manager => Utils}/SourceFileManager.swift (100%) rename PadelClub/{Manager => Utils}/SwiftParser.swift (100%) rename PadelClub/{Manager => Utils}/Tips.swift (100%) rename PadelClub/{Manager => Utils}/URLs.swift (100%) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 2f7369e..e612230 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -613,7 +613,7 @@ C4A47D722B72881500ADC637 /* Views */, FF3F74FD2B91A087004CFE0E /* ViewModel */, C4A47D5F2B6D3B2D00ADC637 /* Data */, - FFF8ACD02B9238A2008466FA /* Manager */, + FFF8ACD02B9238A2008466FA /* Utils */, FFF8ACD72B923F26008466FA /* Extensions */, C425D4042B6D249E002A7B48 /* Assets.xcassets */, FF0EC54D2BB195CA0056B6D1 /* CSV */, @@ -1166,7 +1166,7 @@ path = Components; sourceTree = ""; }; - FFF8ACD02B9238A2008466FA /* Manager */ = { + FFF8ACD02B9238A2008466FA /* Utils */ = { isa = PBXGroup; children = ( FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */, @@ -1182,7 +1182,7 @@ C49EF01A2BD6A1E80077B5AA /* URLs.swift */, FF6EC9072B947A1E00EA7F5A /* Network */, ); - path = Manager; + path = Utils; sourceTree = ""; }; FFF8ACD72B923F26008466FA /* Extensions */ = { diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index b1ac29f..ccb0baf 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -95,7 +95,7 @@ class GroupStage: ModelObject, Storable { } func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { - if let scoreData = _score(forGroupStagePosition: groupStagePosition) { + if let scoreData = _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true) { let hideSetDifference = matchFormat.setsToWin == 1 let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false))) let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: false))) @@ -106,10 +106,10 @@ class GroupStage: ModelObject, Storable { } } - fileprivate func _score(forGroupStagePosition groupStagePosition: Int) -> TeamGroupStageScore? { + fileprivate func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) - if matches.isEmpty { return nil } + if matches.isEmpty && nilIfEmpty { return nil } let wins = matches.filter { $0.winningTeamId == team.id }.count let loses = matches.filter { $0.losingTeamId == team.id }.count let differences = matches.compactMap { $0.scoreDifference(groupStagePosition) } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index d2a4904..a28ed74 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -550,7 +550,7 @@ class Tournament : ModelObject, Storable { func registrationIssues() -> Int { let players : [PlayerRegistration] = unsortedPlayers() let selectedTeams : [TeamRegistration] = selectedSortedTeams() - let callDateIssue : [TeamRegistration] = selectedTeams.filter { isStartDateIsDifferentThanCallDate($0) } + let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } let duplicates : [PlayerRegistration] = duplicates(in: players) let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == -1 }) let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) diff --git a/PadelClub/Manager/CloudConvert.swift b/PadelClub/Utils/CloudConvert.swift similarity index 100% rename from PadelClub/Manager/CloudConvert.swift rename to PadelClub/Utils/CloudConvert.swift diff --git a/PadelClub/Manager/ContactManager.swift b/PadelClub/Utils/ContactManager.swift similarity index 100% rename from PadelClub/Manager/ContactManager.swift rename to PadelClub/Utils/ContactManager.swift diff --git a/PadelClub/Manager/DisplayContext.swift b/PadelClub/Utils/DisplayContext.swift similarity index 100% rename from PadelClub/Manager/DisplayContext.swift rename to PadelClub/Utils/DisplayContext.swift diff --git a/PadelClub/Manager/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift similarity index 100% rename from PadelClub/Manager/FileImportManager.swift rename to PadelClub/Utils/FileImportManager.swift diff --git a/PadelClub/Manager/LocationManager.swift b/PadelClub/Utils/LocationManager.swift similarity index 100% rename from PadelClub/Manager/LocationManager.swift rename to PadelClub/Utils/LocationManager.swift diff --git a/PadelClub/Manager/Network/NetworkFederalService.swift b/PadelClub/Utils/Network/NetworkFederalService.swift similarity index 100% rename from PadelClub/Manager/Network/NetworkFederalService.swift rename to PadelClub/Utils/Network/NetworkFederalService.swift diff --git a/PadelClub/Manager/Network/NetworkManager.swift b/PadelClub/Utils/Network/NetworkManager.swift similarity index 100% rename from PadelClub/Manager/Network/NetworkManager.swift rename to PadelClub/Utils/Network/NetworkManager.swift diff --git a/PadelClub/Manager/Network/NetworkManagerError.swift b/PadelClub/Utils/Network/NetworkManagerError.swift similarity index 100% rename from PadelClub/Manager/Network/NetworkManagerError.swift rename to PadelClub/Utils/Network/NetworkManagerError.swift diff --git a/PadelClub/Manager/NetworkMonitor.swift b/PadelClub/Utils/NetworkMonitor.swift similarity index 100% rename from PadelClub/Manager/NetworkMonitor.swift rename to PadelClub/Utils/NetworkMonitor.swift diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Utils/PadelRule.swift similarity index 100% rename from PadelClub/Manager/PadelRule.swift rename to PadelClub/Utils/PadelRule.swift diff --git a/PadelClub/Manager/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift similarity index 100% rename from PadelClub/Manager/SourceFileManager.swift rename to PadelClub/Utils/SourceFileManager.swift diff --git a/PadelClub/Manager/SwiftParser.swift b/PadelClub/Utils/SwiftParser.swift similarity index 100% rename from PadelClub/Manager/SwiftParser.swift rename to PadelClub/Utils/SwiftParser.swift diff --git a/PadelClub/Manager/Tips.swift b/PadelClub/Utils/Tips.swift similarity index 100% rename from PadelClub/Manager/Tips.swift rename to PadelClub/Utils/Tips.swift diff --git a/PadelClub/Manager/URLs.swift b/PadelClub/Utils/URLs.swift similarity index 100% rename from PadelClub/Manager/URLs.swift rename to PadelClub/Utils/URLs.swift diff --git a/PadelClub/Views/Cashier/CashierDetailView.swift b/PadelClub/Views/Cashier/CashierDetailView.swift index 8942073..7070584 100644 --- a/PadelClub/Views/Cashier/CashierDetailView.swift +++ b/PadelClub/Views/Cashier/CashierDetailView.swift @@ -54,7 +54,7 @@ struct CashierDetailView: View { Text(count.formatted()) } } - } label: { + } label: { Text("Voir le détail") } diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index fd936a8..07edd1e 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -26,8 +26,7 @@ struct GenericDestinationPickerView: View { .padding() .background { Circle() - .fill(Color.master) - .opacity(selectedDestination == nil ? 1.0 : 0.2) + .fill(selectedDestination == nil ? .master : .beige) } .buttonStyle(.plain) } @@ -42,8 +41,7 @@ struct GenericDestinationPickerView: View { .padding() .background { Capsule() - .fill(Color.master) - .opacity(selectedDestination?.id == destination.id ? 1.0 : 0.2) + .fill(selectedDestination?.id == destination.id ? .master : .beige) } .buttonStyle(.plain) .overlay(alignment: .bottomTrailing) { diff --git a/PadelClub/Views/Components/MatchListView.swift b/PadelClub/Views/Components/MatchListView.swift index f78d29c..e54e5af 100644 --- a/PadelClub/Views/Components/MatchListView.swift +++ b/PadelClub/Views/Components/MatchListView.swift @@ -22,7 +22,7 @@ struct MatchListView: View { DisclosureGroup(isExpanded: $isExpanded) { ForEach(matches) { match in MatchRowView(match: match, matchViewStyle: matchViewStyle) - .listRowInsets(EdgeInsets()) + .listRowInsets(EdgeInsets(top: 0, leading: -2, bottom: 0, trailing: 8)) } } label: { LabeledContent { diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 72887eb..4ef7c77 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -382,68 +382,67 @@ struct MatchDetailView: View { } } + @ViewBuilder var startingOptionView: some View { - Section { - - if match.hasEnded() == false { - let rotationDuration = match.getDuration() - Picker(selection: $startDateSetup) { - if match.isReady() { - Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) - Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) - Text("Tout de suite").tag(MatchDateSetup.now) - } - Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration)) - Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration)) - Text("À").tag(MatchDateSetup.customDate) - } label: { - Text("Horaire") + if match.hasEnded() == false { + let rotationDuration = match.getDuration() + Picker(selection: $startDateSetup) { + if match.isReady() { + Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) + Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) + Text("Tout de suite").tag(MatchDateSetup.now) } - .onChange(of: startDateSetup, perform: { value in - switch startDateSetup { - case .customDate: - break - case .now: - startDate = Date() - case .inMinutes(let minutes): - startDate = Date().addingTimeInterval(Double(minutes) * 60) - } - }) + Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration)) + Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration)) + Text("À").tag(MatchDateSetup.customDate) + } label: { + Text("Horaire") } - - if match.startDate != nil || startDateSetup == .customDate { - DatePicker(selection: $startDate) { - Label("Début", systemImage: "calendar").labelStyle(.titleOnly) + .onChange(of: startDateSetup, perform: { value in + switch startDateSetup { + case .customDate: + break + case .now: + startDate = Date() + case .inMinutes(let minutes): + startDate = Date().addingTimeInterval(Double(minutes) * 60) } - .datePickerStyle(.compact) + }) + } + + if match.startDate != nil || startDateSetup == .customDate { + DatePicker(selection: $startDate) { + Label("Début", systemImage: "calendar").labelStyle(.titleOnly) } - - if match.endDate != nil { - DatePicker(selection: $endDate) { - Label("Fin", systemImage: "calendar").labelStyle(.titleOnly) - } - .datePickerStyle(.compact) + .datePickerStyle(.compact) + } + + if match.endDate != nil { + DatePicker(selection: $endDate) { + Label("Fin", systemImage: "calendar").labelStyle(.titleOnly) } + .datePickerStyle(.compact) + } - Picker(selection: $fieldSetup) { - Text("Au hasard").tag(MatchFieldSetup.random) - //Text("Premier disponible").tag(MatchFieldSetup.firstAvailable) - if let tournament = match.currentTournament() { - ForEach(0.. Date: Wed, 1 May 2024 10:14:48 +0200 Subject: [PATCH 2/6] add final ranking view --- PadelClub.xcodeproj/project.pbxproj | 4 + PadelClub/Data/Round.swift | 6 + PadelClub/Data/Tournament.swift | 50 ++++++++- .../GenericDestinationPickerView.swift | 2 +- .../Team/Components/TeamWeightView.swift | 1 - .../Views/Tournament/Screen/Screen.swift | 1 + .../Screen/TournamentRankView.swift | 106 ++++++++++++++++++ .../Views/Tournament/TournamentView.swift | 5 + 8 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 PadelClub/Views/Tournament/Screen/TournamentRankView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index e612230..75bcc61 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -137,6 +137,7 @@ FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; }; + FF5BAF722BE19274008B4B7E /* TournamentRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */; }; FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; }; FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; }; @@ -427,6 +428,7 @@ FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = ""; }; FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalDataViewModel.swift; sourceTree = ""; }; + FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentRankView.swift; sourceTree = ""; }; FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreSheetView.swift; sourceTree = ""; }; FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViewModifier.swift; sourceTree = ""; }; @@ -928,6 +930,7 @@ FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */, FF9268062BCE94D90080F940 /* TournamentCallView.swift */, FF1162802BCF945C000C4809 /* TournamentCashierView.swift */, + FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */, FF8F26522BAE0E4E00650388 /* Components */, ); path = Screen; @@ -1586,6 +1589,7 @@ FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, + FF5BAF722BE19274008B4B7E /* TournamentRankView.swift in Sources */, FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */, FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */, FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */, diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 2038e8f..82b829e 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -337,6 +337,10 @@ class Round: ModelObject, Storable { return seedInterval.localizedLabel(displayStyle) } + func hasNextRound() -> Bool { + nextRound()?.isDisabled() == false + } + func seedInterval() -> SeedInterval? { if parent == nil { let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index + 1) @@ -370,6 +374,8 @@ class Round: ModelObject, Storable { func roundStatus() -> String { if hasStarted() && hasEnded() == false { return "en cours" + } else if hasEnded() { + return "terminée" } else { return "à démarrer" } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index a28ed74..ce5d580 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -322,7 +322,7 @@ class Tournament : ModelObject, Storable { func getActiveRound(withSeeds: Bool = false) -> Round? { let rounds = rounds() - let round = rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first + let round = rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.last(where: { $0.hasEnded() }) ?? rounds.first if withSeeds { if round?.seeds().isEmpty == false { @@ -591,6 +591,54 @@ class Tournament : ModelObject, Storable { return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) } + func finalRanking() -> [Int: [String]] { + var teams: [Int: [String]] = [:] + + let rounds = rounds() + let final = rounds.last?.playedMatches().last + if let winner = final?.winningTeamId { + teams[1] = [winner] + } + if let finalist = final?.losingTeamId { + teams[2] = [finalist] + } + + let others : [Round] = rounds.flatMap { round in + round.loserRoundsAndChildren().filter { $0.isDisabled() == false && $0.hasNextRound() == false } + }.compactMap({ $0 }) + + others.forEach { round in + if let interval = round.seedInterval() { + let playedMatches = round.playedMatches().filter { $0.disabled == false } + let winners = playedMatches.compactMap({ $0.winningTeamId }) + let losers = playedMatches.compactMap({ $0.losingTeamId }) + teams[interval.first + winners.count - 1] = winners + teams[interval.last] = losers + } + } + + let groupStages = groupStages() + let baseRank = teamCount - teamsPerGroupStage * groupStageCount + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + + groupStages.forEach { groupStage in + let groupStageTeams = groupStage.teams(true) + for (index, team) in groupStageTeams.enumerated() { + if team.qualified == false { + let groupStageWidth = max(((index == qualifiedPerGroupStage) ? teamsPerGroupStage - groupStageAdditionalQualified : teamsPerGroupStage) * (index - qualifiedPerGroupStage), 0) + + let _index = baseRank + groupStageWidth + 1 + if let existingTeams = teams[_index] { + teams[_index] = existingTeams + [team.id] + } else { + teams[_index] = [team.id] + } + } + } + } + + + return teams + } func lockRegistration() { closedRegistrationDate = Date() diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 07edd1e..cdae4b9 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -22,7 +22,7 @@ struct GenericDestinationPickerView: View { } label: { Image(systemName: "wrench.and.screwdriver") } - .foregroundStyle(selectedDestination == nil ? .white : .black) + .foregroundStyle(selectedDestination == nil ? .primary : .secondary) .padding() .background { Circle() diff --git a/PadelClub/Views/Team/Components/TeamWeightView.swift b/PadelClub/Views/Team/Components/TeamWeightView.swift index fc48af5..72e1696 100644 --- a/PadelClub/Views/Team/Components/TeamWeightView.swift +++ b/PadelClub/Views/Team/Components/TeamWeightView.swift @@ -21,7 +21,6 @@ struct TeamWeightView: View { if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) { Text("#" + (index + 1).formatted(.number.precision(.integerLength(2...3)))) .monospacedDigit() - .font(.title) } if teamPosition == .two { Text(team.weight.formatted()) diff --git a/PadelClub/Views/Tournament/Screen/Screen.swift b/PadelClub/Views/Tournament/Screen/Screen.swift index d9ffa1e..57c0a67 100644 --- a/PadelClub/Views/Tournament/Screen/Screen.swift +++ b/PadelClub/Views/Tournament/Screen/Screen.swift @@ -16,4 +16,5 @@ enum Screen: String, Codable { case schedule case cashier case call + case rankings } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift new file mode 100644 index 0000000..8dcc0c4 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -0,0 +1,106 @@ +// +// TournamentRankView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 30/04/2024. +// + +import SwiftUI +import LeStorage + +struct TournamentRankView: View { + @Environment(Tournament.self) var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + @State private var rankings: [Int: [TeamRegistration]] = [:] + + var body: some View { + List { + let keys = rankings.keys.sorted() + ForEach(keys, id: \.self) { key in + if let rankedTeams = rankings[key] { + ForEach(rankedTeams) { team in + HStack { + VStack(alignment: .trailing) { + VStack(alignment: .trailing, spacing: -8.0) { + ZStack(alignment: .trailing) { + Text(tournament.teamCount.formatted()).hidden() + Text(key.formatted()) + } + .monospacedDigit() + .font(.largeTitle) + .fontWeight(.bold) + Text(key.ordinalFormattedSuffix()).font(.caption) + } + if let index = tournament.indexOf(team: team) { + let rankingDifference = index - (key - 1) + if rankingDifference > 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.up.fill") + .imageScale(.small) + } + .foregroundColor(.green) + } else if rankingDifference < 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.down.fill") + .imageScale(.small) + } + .foregroundColor(.red) + } else { + Text("--") + } + } + } + + + Divider() + + VStack(alignment: .leading) { + ForEach(team.players()) { player in + VStack(alignment: .leading, spacing: -4.0) { + Text(player.playerLabel()).bold() + HStack(alignment: .firstTextBaseline, spacing: 0.0) { + Text(player.rankLabel()) + if let rank = player.getRank() { + Text(rank.ordinalFormattedSuffix()) + .font(.caption) + } + } + } + } + } + + Spacer() + VStack(alignment: .trailing) { + HStack(alignment: .lastTextBaseline, spacing: 0.0) { + Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always()))) + Text("pts").font(.caption) + } + } + } + } + } + } + } + .listStyle(.grouped) + .onAppear { + let finalRanks = tournament.finalRanking() + finalRanks.keys.sorted().forEach { rank in + if let rankedTeamIds = finalRanks[rank] { + rankings[rank] = rankedTeamIds.compactMap { Store.main.findById($0) } + } + } + } + .navigationTitle("Classement") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } +} + +#Preview { + TournamentRankView() +} diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 4a8f027..7cc9690 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -85,6 +85,8 @@ struct TournamentView: View { TournamentCashierView(tournament: tournament) case .call: TournamentCallView(tournament: tournament) + case .rankings: + TournamentRankView() } } .environment(tournament) @@ -118,6 +120,9 @@ struct TournamentView: View { NavigationLink(value: Screen.structure) { LabelStructure() } + NavigationLink(value: Screen.rankings) { + Text("Classement") + } } } label: { LabelOptions() From e39457f5ea34127f7808b1c5bc71fa3d7ea2f91e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 1 May 2024 13:18:23 +0200 Subject: [PATCH 3/6] update groupstage views and fix lag --- PadelClub/Data/GroupStage.swift | 17 + PadelClub/Data/Match.swift | 9 +- .../GenericDestinationPickerView.swift | 2 +- .../Views/Components/MatchListView.swift | 2 +- .../GroupStage/GroupStageSettingsView.swift | 209 ++----------- .../Views/GroupStage/GroupStageTeamView.swift | 56 +--- .../Views/GroupStage/GroupStageView.swift | 290 ++++++------------ .../Views/GroupStage/GroupStagesView.swift | 8 +- .../Components/EditablePlayerView.swift | 7 + PadelClub/Views/Score/EditScoreView.swift | 7 +- .../Tournament/TournamentRunningView.swift | 2 +- .../Views/Tournament/TournamentView.swift | 2 +- .../ViewModifiers/ListRowViewModifier.swift | 8 +- 13 files changed, 185 insertions(+), 434 deletions(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index ccb0baf..d23ab3d 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -94,6 +94,23 @@ class GroupStage: ModelObject, Storable { } } + func updateGroupStageState() { + if hasEnded(), let tournament = tournamentObject() { + do { + let teams = teams(true) + for (index, team) in teams { + team.qualified = index < tournament.qualifiedPerGroupStage + if team.bracketPosition != nil && team.qualified == false { + team.bracketPosition = nil + } + } + try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } + } + } + func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { if let scoreData = _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true) { let hideSetDifference = matchFormat.setsToWin == 1 diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index cf19e34..4496dfc 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -348,10 +348,7 @@ class Match: ModelObject, Storable { winningTeamId = teamScoreWinning.teamRegistration losingTeamId = teamScoreWalkout.teamRegistration - - // matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false) - // matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false) - // matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex) + groupStageObject?.updateGroupStageState() } func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { @@ -359,11 +356,9 @@ class Match: ModelObject, Storable { if endDate == nil { endDate = Date() } - // matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false) - // matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false) - // matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex) winningTeamId = team(matchDescriptor.winner)?.id losingTeamId = team(matchDescriptor.winner.otherTeam)?.id + groupStageObject?.updateGroupStageState() } func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index cdae4b9..1db550d 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -21,8 +21,8 @@ struct GenericDestinationPickerView: View { selectedDestination = nil } label: { Image(systemName: "wrench.and.screwdriver") + .foregroundColor(selectedDestination == nil ? .white : .black) } - .foregroundStyle(selectedDestination == nil ? .primary : .secondary) .padding() .background { Circle() diff --git a/PadelClub/Views/Components/MatchListView.swift b/PadelClub/Views/Components/MatchListView.swift index e54e5af..c1785f5 100644 --- a/PadelClub/Views/Components/MatchListView.swift +++ b/PadelClub/Views/Components/MatchListView.swift @@ -13,7 +13,7 @@ struct MatchListView: View { let matches: [Match] var matchViewStyle: MatchViewStyle = .sectionedStandardStyle - @State private var isExpanded: Bool = true + @State var isExpanded: Bool = true @ViewBuilder var body: some View { diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index c42cb38..5f3cbb5 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct GroupStageSettingsView: View { @EnvironmentObject var dataStore: DataStore @@ -25,17 +26,16 @@ struct GroupStageSettingsView: View { var body: some View { List { - Toggle(isOn: $nameAlphabetical) { - Text("Nommer les poules alphabétiquement") - } - Menu { - //menuAddGroupStage + Section { menuBuildAllGroupStages + } + + Section { menuGenerateGroupStage(.random) + } + + Section { menuGenerateGroupStage(.snake) - //menuGenerateGroupStage(.swiss) - } label: { - LabelOptions() } if tournament.missingQualifiedFromGroupStages().isEmpty == false && tournament.qualifiedTeams().count >= tournament.qualifiedFromGroupStage() && tournament.groupStageAdditionalQualified > 0 { @@ -52,115 +52,9 @@ struct GroupStageSettingsView: View { } } -// if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) { -// Section { -// RowButtonView("Reconstruire les poules") { -// confirmGroupStageRebuild = true -// } -// .modify { -// if UIDevice.current.userInterfaceIdiom == .pad { -// $0.alert("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) { -// -// Button(role: .destructive) { -// tournament.refreshGroupStages() -// save() -// } label: { -// Text("Reconstruire") -// } -// -// -// Button(role: .cancel) { -// -// } label: { -// Text("Annuler") -// } -// } message: { -// Text("Attention, cela peut modifier les poules existants.") -// -// } -// } else { -// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) { -// Button(role: .destructive) { -// tournament.refreshGroupStages() -// save() -// } label: { -// Text("Reconstruire") -// } -// } message: { -// Text("Attention, cela peut modifier les poules existants.") -// } -// } -// } -// } header: { -// Text("Erreur détectée") -// } -// } -// - -// if tournament.isRoundSwissTournament() == false && (tournament.orderedGroupStages.allSatisfy({ $0.orderedMatches.count > 0 }) == false && tournament.groupStagesAreOver == false && tournament.groupStagesCount > 0) { -// Section { -// RowButtonView("Générer les matchs de poules") { -// startAllGroupStageConfirmation = true -// } -// .modify { -// if UIDevice.current.userInterfaceIdiom == .pad { -// $0.alert("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) { -// Button("Générer") { -// tournament.orderedGroupStages.forEach { -// if $0.orderedMatches.isEmpty { -// $0.startGroupStage() -// } -// } -// save() -// } -// Button(role: .cancel) { -// -// } label: { -// Text("Annuler") -// } -// } -// -// } else { -// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) { -// Button("Générer") { -// tournament.orderedGroupStages.forEach { -// if $0.orderedMatches.isEmpty { -// $0.startGroupStage() -// } -// } -// save() -// } -// } -// } -// } -// } -// } -// - if tournament.groupStagesAreOver() == false { -// Section { -// GroupStageMatchAvailableToStartView(tournament: tournament, groupStageIndex: selectedGroupStageIndex) -// } header: { -// if selectedGroupStageIndex == -1 { -// Text("Matchs de poules prêt à démarrer") -// } else { -// Text("Matchs de la poule \(selectedGroupStageIndex) prêt à démarrer") -// } -// } footer: { -// Text("présence d'au moins 2 équipes d'une même poule ayant réglé.") -// } + Toggle(isOn: $nameAlphabetical) { + Text("Nommer les poules alphabétiquement") } - -// if tournament.teamsPerGroupStage == 3 && tournament.qualifiedPerGroupStage == 1 && tournament.numberOfGroupStages%2 == 0 && tournament.moreQualifiedFromGroupStages == 0 { -// Section { -// NavigationLink { -// GroupStageMissingMatchView(tournament: tournament) -// } label: { -// Text("Matchs de classement de poules") -// } -// } -// } - - } .onChange(of: nameAlphabetical) { let groupStages = tournament.groupStages() @@ -178,71 +72,30 @@ struct GroupStageSettingsView: View { } - var menuBuildAllGroupStages: some View { - Button(role: .destructive) { - tournament.deleteGroupStages() - tournament.buildGroupStages() - } label: { - Label("Refaire les poules", systemImage: "restart") - } + var menuBuildAllGroupStages: some View { + RowButtonView("Refaire les poules", role: .destructive) { + tournament.deleteGroupStages() + tournament.buildGroupStages() + _save() } - - @ViewBuilder - func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View { - Button(role: .destructive) { - tournament.groupStageOrderingMode = mode - tournament.refreshGroupStages() - //save() - } label: { - Label("Poule \(mode.localizedLabel().lowercased())", systemImage: mode.systemImage) - } + } + + @ViewBuilder + func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View { + RowButtonView("Poule \(mode.localizedLabel().lowercased())", role: .destructive, systemImage: mode.systemImage) { + tournament.groupStageOrderingMode = mode + tournament.refreshGroupStages() + _save() } + } -// func addGroupStage(_ size: Int64) { -// let groupStage = GroupStage(context: viewContext) -// groupStage.index = tournament.firstIndexToUseForNewGroupStage -// groupStage.size = Int64(size) -// groupStage.matchFormatRawValue = tournament.groupStageMatchFormatRawValue -// print("addGroupStage groupStagesCount", tournament.groupStagesCount) -// print("addGroupStage numberOfGroupStages", tournament.numberOfGroupStages) -// if tournament.groupStagesCount >= tournament.numberOfGroupStages { -// tournament.numberOfGroupStages += 1 -// } -// tournament.addToGroupStages(groupStage) -// save() -// } -// -// var menuAddGroupStage: some View { -// Menu { -// ForEach(-1...1) { index in -// let i = tournament.teamsPerGroupStage + Int64(index) -// Button { -// addGroupStage(i) -// } label: { -// Text("Poule de \(i)") -// } -// .disabled(i < 2) -// } -// } label: { -// Label("Ajouter une poule", systemImage: "server.rack") -// } -// -// } - - -// func save() { -// do { -// tournament.objectWillChange.send() -// try viewContext.save() -// viewContext.refreshAllObjects() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// } - + private func _save() { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } } #Preview { diff --git a/PadelClub/Views/GroupStage/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/GroupStageTeamView.swift index 5dec47f..7653ef3 100644 --- a/PadelClub/Views/GroupStage/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/GroupStageTeamView.swift @@ -15,21 +15,9 @@ struct GroupStageTeamView: View { var body: some View { List { - ForEach(team.players()) { player in - Section { - ImportedPlayerView(player: player) - } footer: { - HStack { - Button("contacter") { - } - Spacer() - Button { - player.hasArrived.toggle() - try? dataStore.playerRegistrations.addOrUpdate(instance: player) - } label: { - Label("présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle") - } - } + Section { + ForEach(team.players()) { player in + EditablePlayerView(player: player, editingOptions: [.licenceId, .payment]) } } @@ -38,7 +26,7 @@ struct GroupStageTeamView: View { if team.qualified == false { RowButtonView("Qualifier l'équipe") { team.qualified = true - team.bracketPosition = nil +// team.bracketPosition = nil _save() } } @@ -54,41 +42,13 @@ struct GroupStageTeamView: View { } Section { - // if let deltaLabel = bracket.tournament?.deltaLabel(index, bracketIndex: bracket.index.intValue) { - // Section { - // Button(role: .destructive) { - // entrant.resetBracketPosition() - // save() - // } label: { - // Text(deltaLabel) - // } - // Divider() - // } header: { - // Text("Toute l'équipe, poids: " + entrant.updatedRank.formatted()) - // } - // } - // - // ForEach(entrant.orderedPlayers) { player in - // if let deltaLabel = bracket.tournament?.playerDeltaLabel(rank: entrant.otherPlayer(player)?.currentRank, positionInBracket: index, bracketIndex: bracket.index.intValue) { - // Section { - // Button(role: .destructive) { - // entrant.team?.removeFromPlayers(player) - // save() - // } label: { - // Text(deltaLabel) - // } - // Divider() - // } header: { - // Text(player.longLabel + ", rang: " + player.formattedRank) - // } - // } - // } - } header: { - Text("Remplacement") + RowButtonView("Remplacement") { + + } } Section { - RowButtonView("Retirer de la poule") { + RowButtonView("Retirer de la poule", role: .destructive) { team.groupStagePosition = nil team.groupStage = nil _save() diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 2e6524f..ab74b59 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct GroupStageView: View { @EnvironmentObject var dataStore: DataStore @@ -15,21 +16,7 @@ struct GroupStageView: View { @State private var confirmRemoveAll: Bool = false @State private var confirmResetMatch: Bool = false @State private var groupStageName: String = "" - - private enum GroupStageSortingMode { - case auto - case score - case weight - } - - var sortByScore: Bool { - sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score - } - - func teamAt(atIndex index: Int) -> TeamRegistration? { - sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index) - } - + init(groupStage: GroupStage) { self.groupStage = groupStage _groupStageName = State(wrappedValue: groupStage.groupStageTitle()) @@ -38,7 +25,7 @@ struct GroupStageView: View { var body: some View { List { Section { - _groupStageView() + GroupStageScoreView(groupStage: groupStage, sortByScore: _sortByScore) } header: { if let startDate = groupStage.startDate { Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formattedAsHourMinute()) @@ -47,19 +34,9 @@ struct GroupStageView: View { HStack { Spacer() Button { - if sortingMode == .auto { - if groupStage.hasEnded() { - sortingMode = .weight - } else { - sortingMode = .score - } - } else if sortingMode == .weight { - sortingMode = .score - } else { - sortingMode = .weight - } + _updateSortingMode() } label: { - Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly) + Label(_sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly) .underline() } .buttonStyle(.borderless) @@ -70,11 +47,11 @@ struct GroupStageView: View { MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID()) MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID()) MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID()) - MatchListView(section: "terminés", matches: groupStage.finishedMatches()).id(UUID()) + MatchListView(section: "terminés", matches: groupStage.finishedMatches(), isExpanded: false).id(UUID()) } .onChange(of: groupStageName) { groupStage.name = groupStageName - try? dataStore.groupStages.addOrUpdate(instance: groupStage) + _save() } .toolbar { ToolbarItem(placement: .topBarTrailing) { @@ -84,65 +61,111 @@ struct GroupStageView: View { .navigationTitle($groupStageName) } - private func _groupStageView() -> some View { - ForEach(0..<(groupStage.size), id: \.self) { index in - if let team = teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition { - NavigationLink { - GroupStageTeamView(groupStage: groupStage, team: team) - } label: { - HStack(alignment: .center) { + private enum GroupStageSortingMode { + case auto + case score + case weight + } + + private var _sortByScore: Bool { + sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score + } + + private func _updateSortingMode() { + if sortingMode == .auto { + if groupStage.hasEnded() { + sortingMode = .weight + } else { + sortingMode = .score + } + } else if sortingMode == .weight { + sortingMode = .score + } else { + sortingMode = .weight + } + } + + struct GroupStageScoreView: View { + @EnvironmentObject var dataStore: DataStore + let groupStage: GroupStage + let sortByScore: Bool + let teams: [TeamRegistration] + + init(groupStage: GroupStage, sortByScore: Bool) { + self.groupStage = groupStage + self.sortByScore = sortByScore + self.teams = groupStage.teams(sortByScore) + } + + private func _teamAt(atIndex index: Int) -> TeamRegistration? { + sortByScore ? teams[safe: index] : groupStage.teamAt(groupStagePosition: index) + } + + var body: some View { + ForEach(0..<(groupStage.size), id: \.self) { index in + if let team = _teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition { + NavigationLink { + GroupStageTeamView(groupStage: groupStage, team: team) + } label: { VStack(alignment: .leading, spacing: 4.0) { HStack(spacing: 6.0) { Text("#\(groupStagePosition + 1)") Text("Poids \(team.weight)") + Spacer() + if team.qualified { + Text("qualifié") + } } .font(.footnote) HStack { if let teamName = team.name { - Text(teamName) + Text(teamName).font(.title) } else { VStack(alignment: .leading) { ForEach(team.players()) { player in - Text(player.playerLabel()) + Text(player.playerLabel()).lineLimit(1) } } } - - if team.qualified { - Image(systemName: "checkmark.seal") - } - } - } - Spacer() - if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { - VStack(alignment: .center) { - HStack(spacing: 0.0) { - Text(score.wins) - Text("/") - Text(score.losses) - }.bold() - if let setsDifference = score.setsDifference { - Text(setsDifference + " sets") - } - if let gamesDifference = score.gamesDifference { - Text(gamesDifference + " jeux") + Spacer() + if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { + VStack(alignment: .trailing) { + HStack(spacing: 0.0) { + Text(score.wins) + Text("/") + Text(score.losses) + }.font(.headline).monospacedDigit() + if let setsDifference = score.setsDifference { + HStack(spacing: 4.0) { + Text(setsDifference) + Text("sets") + }.font(.footnote) + } + if let gamesDifference = score.gamesDifference { + HStack(spacing: 4.0) { + Text(gamesDifference) + Text("jeux") + }.font(.footnote) + } + } } } } } - } - } else { - HStack(alignment: .center) { + .listRowView(isActive: team.qualified, color: .master) + } else { VStack(alignment: .leading, spacing: 0) { - HStack { - Text("#\(index + 1)") - } - .font(.caption) + Text("#\(index + 1)") + .font(.caption) TeamPickerView(teamPicked: { team in print(team.pasteData()) team.groupStage = groupStage.id team.groupStagePosition = index - try? dataStore.teamRegistrations.addOrUpdate(instance: team) + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } }) } } @@ -156,121 +179,15 @@ struct GroupStageView: View { Button("Retirer le nom") { groupStage.name = nil groupStageName = groupStage.groupStageTitle() - try? dataStore.groupStages.addOrUpdate(instance: groupStage) + _save() } } - -// if groupStage.playedMatches().isEmpty { -// Button { -// //groupStage.startGroupStage() -// //save() -// } label: { -// Text("Créer les matchs") -// } -// .buttonStyle(.borderless) -// } - Button("Retirer tout le monde", role: .destructive) { confirmRemoveAll = true } Button("Recommencer tous les matchs", role: .destructive) { confirmResetMatch = true } - - // Button { - // selectedMenuLink = .prepare - // } label: { - // Label("Préparer", systemImage: "calendar") - // } - // - // Menu { - // MenuWarnView(warningSender: groupStage) - // } label: { - // Label("Prévenir", systemImage: "person.crop.circle") - // } - // - // if groupStage.isBroadcasted() { - // Button { - // groupStage.refreshBroadcast() - // } label: { - // Label("Rafraîchir", systemImage: "arrow.up.circle.fill") - // } - // Button { - // groupStage.stopBroadcast() - // save() - // } label: { - // Label("Arrêter la diffusion", systemImage: "stop.circle.fill") - // } - // } else if groupStage.tournament?.canBroadcast() == true { - // Button { - // Task { - // try? await groupStage.broadcastGroupStage() - // save() - // } - // } label: { - // Label("Diffuser", systemImage: "airplayvideo") - // } - // } - // - // Divider() - // if groupStage.tournament?.canBroadcast() == true { - // Menu { - // Button { - // Task { - // try? await groupStage.broadcastGroupStageMatches() - // save() - // } - // } label: { - // Label("Diffuser", systemImage: "airplayvideo") - // } - // - // Button { - // groupStage.refreshBroadcastMatches() - // } label: { - // Label("Rafraîchir", systemImage: "arrow.up.circle.fill") - // } - // Button { - // groupStage.stopBroadcastMatches() - // save() - // } label: { - // Label("Arrêter la diffusion", systemImage: "stop.circle.fill") - // } - // } label: { - // Text("Diffusion des matchs") - // } - // } - // - // Divider() - // Menu { - // if groupStage.orderedMatches.isEmpty == false { - // Button(role: .destructive) { - // groupStage.startGroupStage() - // save() - // } label: { - // Label("Re-démarrer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash") - // } - // } - // - // if groupStage.orderedMatches.isEmpty == false { - // Button(role: .destructive) { - // groupStage.removeMatches() - // save() - // } label: { - // Label("Supprimer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash") - // } - // } - // - // Button(role: .destructive) { - // groupStage.tournament?.completeEntries.filter { $0.groupStagePosition == groupStage.index }.forEach { $0.resetGroupStagePosition() } - // groupStage.tournament?.removeFromGroupStages(groupStage) - // groupStage.tournament?.numberOfGroupStages -= 1 - // save() - // } label: { - // Label("Supprimer la \(groupStage.titleLabel.lowercased())", systemImage: "trash") - // } - // } label: { - // Text("Éditer") - // } } label: { LabelOptions() } @@ -281,7 +198,11 @@ struct GroupStageView: View { team.groupStagePosition = nil team.groupStage = nil } - try? dataStore.teamRegistrations.addOrUpdate(contentOfs: teams) + do { + try dataStore.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } } } .confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) { @@ -292,18 +213,11 @@ struct GroupStageView: View { } -// func save() { -// do { -// groupStage.objectWillChange.send() -// groupStage.tournament?.orderedGroupStages.forEach { $0.objectWillChange.send() } -// groupStage.tournament?.objectWillChange.send() -// -// try viewContext.save() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// } + private func _save() { + do { + try dataStore.groupStages.addOrUpdate(instance: groupStage) + } catch { + Logger.error(error) + } + } } diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index f98d4b1..3628cb4 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -73,10 +73,10 @@ struct GroupStagesView: View { let runningMatches = allGroupStages.flatMap({ $0.runningMatches() }) let readyMatches = allGroupStages.flatMap({ $0.readyMatches() }) let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() }) - MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle) - MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle) - MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle) - MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle) + MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) + MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) + MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false) + MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false) } .navigationTitle("Toutes les poules") case .groupStage(let groupStage): diff --git a/PadelClub/Views/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index b75a617..788cc60 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -74,6 +74,13 @@ struct EditablePlayerView: View { } Section { + Button { + player.hasArrived.toggle() + try? dataStore.playerRegistrations.addOrUpdate(instance: player) + } label: { + Label("présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle") + } + Button { editedLicenceId = player.licenceId ?? "" shouldPresentLicenceIdEdition = true diff --git a/PadelClub/Views/Score/EditScoreView.swift b/PadelClub/Views/Score/EditScoreView.swift index a1bba95..58d93a7 100644 --- a/PadelClub/Views/Score/EditScoreView.swift +++ b/PadelClub/Views/Score/EditScoreView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct EditScoreView: View { @EnvironmentObject var dataStore: DataStore @@ -102,7 +103,11 @@ struct EditScoreView: View { func save() { if let match = matchDescriptor.match { - try? dataStore.matches.addOrUpdate(instance: match) + do { + try dataStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } } } } diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 7ec5e46..95e360f 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -75,7 +75,7 @@ struct TournamentRunningView: View { MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches)) // MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches)) // MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches)) - MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches)) + MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches), isExpanded: false) } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 7cc9690..e13f048 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -45,7 +45,7 @@ struct TournamentView: View { } } } footer: { - if tournament.inscriptionClosed() == false && tournament.state() == .build && tournament.unsortedTeams().isEmpty == false { + if tournament.inscriptionClosed() == false && tournament.state() == .build && tournament.unsortedTeams().isEmpty == false && tournament.hasStarted() == false { Button { tournament.lockRegistration() _save() diff --git a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift index 6c21d80..ac26e22 100644 --- a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift +++ b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift @@ -8,11 +8,11 @@ import SwiftUI struct ListRowViewModifier: ViewModifier { - @State private var isActived = true + let isActive: Bool let color: Color func body(content: Content) -> some View { - if isActived { + if isActive { content .listRowBackground( color.variation() @@ -27,7 +27,7 @@ struct ListRowViewModifier: ViewModifier { } extension View { - func listRowView(color: Color) -> some View { - modifier(ListRowViewModifier(color: color)) + func listRowView(isActive: Bool = true, color: Color) -> some View { + modifier(ListRowViewModifier(isActive: isActive, color: color)) } } From 5c534fe22e6877b71cbfee02849355ac7275f331 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 1 May 2024 13:19:37 +0200 Subject: [PATCH 4/6] settings --- PadelClub.xcodeproj/project.pbxproj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 75bcc61..4cb3d70 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1775,6 +1775,8 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1785,7 +1787,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.1; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1807,6 +1809,8 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1817,7 +1821,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.1; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; From a7f8b1bafd5250820bce5aa573bea4c55d703341 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 1 May 2024 14:16:16 +0200 Subject: [PATCH 5/6] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 4 ++ PadelClub/Data/GroupStage.swift | 19 +++--- PadelClub/Data/MockData.swift | 16 ----- PadelClub/Data/Tournament.swift | 17 ++++- PadelClub/Views/Cashier/CashierView.swift | 16 +++-- .../Views/GroupStage/GroupStageView.swift | 6 +- .../Navigation/Agenda/EventListView.swift | 15 ----- PadelClub/Views/Round/RoundSettingsView.swift | 20 +++--- .../TournamentGeneralSettingsView.swift | 25 +------ .../Components/TournamentStatusView.swift | 66 +++++++++++++++++++ .../Screen/TournamentCashierView.swift | 14 +++- .../Screen/TournamentSettingsView.swift | 9 ++- 12 files changed, 140 insertions(+), 87 deletions(-) create mode 100644 PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 4cb3d70..864564a 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; + FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; @@ -444,6 +445,7 @@ FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = ""; }; FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = ""; }; FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = ""; }; + FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = ""; }; FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; @@ -1062,6 +1064,7 @@ FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */, FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */, FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */, + FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */, ); path = Components; sourceTree = ""; @@ -1497,6 +1500,7 @@ FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */, FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */, + FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */, FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */, FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */, FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index d23ab3d..5db8453 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -98,7 +98,7 @@ class GroupStage: ModelObject, Storable { if hasEnded(), let tournament = tournamentObject() { do { let teams = teams(true) - for (index, team) in teams { + for (index, team) in teams.enumerated() { team.qualified = index < tournament.qualifiedPerGroupStage if team.bracketPosition != nil && team.qualified == false { team.bracketPosition = nil @@ -111,8 +111,8 @@ class GroupStage: ModelObject, Storable { } } - func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { - if let scoreData = _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true) { + func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { + if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) { let hideSetDifference = matchFormat.setsToWin == 1 let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false))) let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: false))) @@ -123,7 +123,7 @@ class GroupStage: ModelObject, Storable { } } - fileprivate func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { + func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) if matches.isEmpty && nilIfEmpty { return nil } @@ -225,8 +225,9 @@ class GroupStage: ModelObject, Storable { } fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool - fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) - + + typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) + fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool { let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() let combos = Array((0.. [TeamRegistration] { + func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { if sortedByScore { - return unsortedTeams().compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in + return unsortedTeams().compactMap({ team in + scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePosition!) + }).sorted { (lhs, rhs) in let predicates: [TeamScoreAreInIncreasingOrder] = [ { $0.wins < $1.wins }, { $0.setDifference < $1.setDifference }, diff --git a/PadelClub/Data/MockData.swift b/PadelClub/Data/MockData.swift index 11230c9..13dce73 100644 --- a/PadelClub/Data/MockData.swift +++ b/PadelClub/Data/MockData.swift @@ -45,22 +45,6 @@ extension Tournament { static func mock() -> Tournament { Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) } - - static func newEmptyInstance() -> Tournament { - let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource - var _mostRecentDateAvailable: Date? { - guard let lastDataSource else { return nil } - return URL.importDateFormatter.date(from: lastDataSource) - } - - let rankSourceDate = _mostRecentDateAvailable - let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed() - let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) - let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) - let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) - - return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) - } } extension Match { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index ce5d580..5e06663 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1218,7 +1218,22 @@ extension Tournament: TournamentBuildHolder { } extension Tournament { - + static func newEmptyInstance() -> Tournament { + let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource + var _mostRecentDateAvailable: Date? { + guard let lastDataSource else { return nil } + return URL.importDateFormatter.date(from: lastDataSource) + } + + let rankSourceDate = _mostRecentDateAvailable + let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed() + let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) + let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) + let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) + + return Tournament(creator: DataStore.shared.user?.id, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) + } + static func fake() -> Tournament { return Tournament(event: "Roland Garros", creator: "", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, seedCount: 8, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) } diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index 2660f02..a20c4fe 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -17,15 +17,15 @@ struct CashierView: View { @State private var sortOrder: SortOrder = .ascending @State private var searchText = "" @State private var isSearching: Bool = false - - init(event: Event) { - self.tournaments = event.tournaments - self.teams = [] - } - + init(tournament: Tournament, teams: [TeamRegistration]) { self.tournaments = [tournament] self.teams = teams + if teams.filter { $0.callDate != nil }.isEmpty { + _sortOption = .init(wrappedValue: .teamRank) + } else { + _sortOption = .init(wrappedValue: .callDate) + } } private func _sharedData() -> String { @@ -284,7 +284,9 @@ struct CashierView: View { private func _isContentUnavailable() -> Bool { switch sortOption { - case .teamRank, .callDate: + case .callDate: + return teams.filter({ $0.callDate != nil && _shouldDisplayTeam($0) }).isEmpty + case .teamRank: return teams.filter({ _shouldDisplayTeam($0) }).isEmpty default: return teams.flatMap({ $0.players() }).filter({ _shouldDisplayPlayer($0) }).isEmpty diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index ab74b59..8c61b51 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -89,12 +89,14 @@ struct GroupStageView: View { @EnvironmentObject var dataStore: DataStore let groupStage: GroupStage let sortByScore: Bool + let scores: [GroupStage.TeamGroupStageScore]? let teams: [TeamRegistration] init(groupStage: GroupStage, sortByScore: Bool) { self.groupStage = groupStage self.sortByScore = sortByScore - self.teams = groupStage.teams(sortByScore) + self.scores = sortByScore ? (0.. TeamRegistration? { @@ -128,7 +130,7 @@ struct GroupStageView: View { } } Spacer() - if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { + if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition, score: scores?.first(where: { $0.team.groupStagePosition == groupStagePosition })) { VStack(alignment: .trailing) { HStack(spacing: 0.0) { Text(score.wins) diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index c8267d3..944f01d 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -100,21 +100,6 @@ struct EventListView: View { NavigationLink(value: tournament) { TournamentCellView(tournament: tournament) } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive) { - - tournament.isDeleted = true - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } - -// try? dataStore.tournaments.delete(instance: tournament) - } label: { - LabelDelete() - } - } .contextMenu { Button { diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 1b535ec..f57f499 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -14,16 +14,16 @@ struct RoundSettingsView: View { var body: some View { List { - Section { - RowButtonView("Enabled", role: .destructive) { - let allMatches = tournament._allMatchesIncludingDisabled() - allMatches.forEach({ - $0.disabled = false - $0.byeState = false - }) - try? dataStore.matches.addOrUpdate(contentOfs: allMatches) - } - } +// Section { +// RowButtonView("Enabled", role: .destructive) { +// let allMatches = tournament._allMatchesIncludingDisabled() +// allMatches.forEach({ +// $0.disabled = false +// $0.byeState = false +// }) +// try? dataStore.matches.addOrUpdate(contentOfs: allMatches) +// } +// } Section { RowButtonView("Retirer toutes les têtes de séries", role: .destructive) { tournament.unsortedTeams().forEach({ $0.bracketPosition = nil }) diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 864caa1..56997ce 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -17,27 +17,7 @@ struct TournamentGeneralSettingsView: View { var body: some View { @Bindable var tournament = tournament - Form { - Section { - if tournament.hasEnded() == false { - if tournament.isCanceled == false { - RowButtonView("Annuler le tournoi", role: .destructive) { - tournament.isCanceled.toggle() - } - } else { - RowButtonView("Reprendre le tournoi", role: .destructive) { - tournament.isCanceled.toggle() - } - } - } - - Toggle(isOn: $tournament.isPrivate) { - Text("Tournoi privée") - } - } footer: { - Text("Le tournoi sera masqué sur le site padelclub.app") - } - + Form { Section { TournamentDatePickerView() TournamentDurationManagerView() @@ -121,9 +101,6 @@ struct TournamentGeneralSettingsView: View { ]) { _save() } - .onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) { - _save() - } } private func _save() { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift new file mode 100644 index 0000000..31f2fce --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -0,0 +1,66 @@ +// +// TournamentStatusView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/05/2024. +// + +import SwiftUI +import LeStorage + +struct TournamentStatusView: View { + @Environment(Tournament.self) private var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + var body: some View { + @Bindable var tournament = tournament + Form { + Section { + RowButtonView("Supprimer le tournoi", role: .destructive) { + tournament.isDeleted.toggle() + } + } footer: { + Text("todo: expliquer ce que ca fait") + } + Section { + if tournament.hasEnded() == false { + if tournament.isCanceled == false { + RowButtonView("Annuler le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + } + } else { + RowButtonView("Reprendre le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + } + } + } + } footer: { + Text("todo: expliquer ce que ca fait") + } + + Section { + Toggle(isOn: $tournament.isPrivate) { + Text("Tournoi privée") + } + } footer: { + Text("Le tournoi sera masqué sur le site padelclub.app") + } + } + .toolbarBackground(.visible, for: .navigationBar) + .onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) { + _save() + } + } + + private func _save() { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } +} + +#Preview { + TournamentStatusView() +} diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 4b1e91f..fffdb15 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -71,6 +71,12 @@ struct TournamentCashierView: View { func allDestinations() -> [CashierDestination] { var allDestinations : [CashierDestination] = [] + let tournamentHasEnded = tournament.hasEnded() + if tournamentHasEnded { + allDestinations.append(.summary) + allDestinations.append(.all(tournament)) + } + let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } allDestinations.append(contentsOf: destinations) tournament.rounds().forEach { round in @@ -78,8 +84,12 @@ struct TournamentCashierView: View { allDestinations.append(CashierDestination.bracket(round)) } } - allDestinations.append(.all(tournament)) - allDestinations.append(.summary) + + if tournamentHasEnded == false { + allDestinations.append(.all(tournament)) + allDestinations.append(.summary) + } + return allDestinations } diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index a984d14..3cd848e 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -8,6 +8,7 @@ import SwiftUI enum TournamentSettings: Identifiable, Selectable { + case status case general case club(Tournament) case matchFormats @@ -16,8 +17,10 @@ enum TournamentSettings: Identifiable, Selectable { func selectionLabel() -> String { switch self { + case .status: + return "Statut" case .matchFormats: - return "Formats de jeu" + return "Formats" case .general: return "Général" case .club: @@ -48,13 +51,15 @@ struct TournamentSettingsView: View { @Environment(Tournament.self) var tournament: Tournament private func destinations() -> [TournamentSettings] { - [.general, .club(tournament), .matchFormats] + [.status, .general, .club(tournament), .matchFormats] } var body: some View { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: destinations(), nilDestinationIsValid: false) switch selectedDestination! { + case .status: + TournamentStatusView() case .matchFormats: TournamentMatchFormatsSettingsView() case .general: From 437d408c6fc668a4c8495021ceba3e85ea796e4d Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 1 May 2024 14:29:07 +0200 Subject: [PATCH 6/6] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 4 ++++ PadelClub/Data/Match.swift | 2 ++ PadelClub/Data/Round.swift | 11 ++++++++++ .../Tournament/Screen/BroadcastView.swift | 22 +++++++++++++++++++ .../Views/Tournament/Screen/Screen.swift | 1 + .../Views/Tournament/TournamentView.swift | 16 ++++++++++++++ 6 files changed, 56 insertions(+) create mode 100644 PadelClub/Views/Tournament/Screen/BroadcastView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 864564a..0bff72b 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -154,6 +154,7 @@ FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; + FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; @@ -446,6 +447,7 @@ FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = ""; }; FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = ""; }; FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = ""; }; + FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastView.swift; sourceTree = ""; }; FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; @@ -933,6 +935,7 @@ FF9268062BCE94D90080F940 /* TournamentCallView.swift */, FF1162802BCF945C000C4809 /* TournamentCashierView.swift */, FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */, + FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */, FF8F26522BAE0E4E00650388 /* Components */, ); path = Screen; @@ -1450,6 +1453,7 @@ FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, + FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */, FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */, diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 4496dfc..de0a683 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -349,6 +349,7 @@ class Match: ModelObject, Storable { winningTeamId = teamScoreWinning.teamRegistration losingTeamId = teamScoreWalkout.teamRegistration groupStageObject?.updateGroupStageState() + roundObject?.updateTournamentState() } func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { @@ -359,6 +360,7 @@ class Match: ModelObject, Storable { winningTeamId = team(matchDescriptor.winner)?.id losingTeamId = team(matchDescriptor.winner.otherTeam)?.id groupStageObject?.updateGroupStageState() + roundObject?.updateTournamentState() } func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 82b829e..a5851fc 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -370,6 +370,17 @@ class Round: ModelObject, Storable { } return RoundRule.roundName(fromRoundIndex: index) } + + func updateTournamentState() { + if let tournamentObject = tournamentObject(), index == 0, isUpperBracket(), hasEnded() { + tournamentObject.endDate = Date() + do { + try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) + } catch { + Logger.error(error) + } + } + } func roundStatus() -> String { if hasStarted() && hasEnded() == false { diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift new file mode 100644 index 0000000..2d6ace4 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -0,0 +1,22 @@ +// +// BroadcastView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/05/2024. +// + +import SwiftUI + +struct BroadcastView: View { + var body: some View { + List { + } + .navigationTitle("Diffusion") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } +} + +#Preview { + BroadcastView() +} diff --git a/PadelClub/Views/Tournament/Screen/Screen.swift b/PadelClub/Views/Tournament/Screen/Screen.swift index 57c0a67..05ee93b 100644 --- a/PadelClub/Views/Tournament/Screen/Screen.swift +++ b/PadelClub/Views/Tournament/Screen/Screen.swift @@ -17,4 +17,5 @@ enum Screen: String, Codable { case cashier case call case rankings + case broadcast } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index e13f048..ac9806a 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -60,8 +60,19 @@ struct TournamentView: View { switch tournament.state() { case .initial: TournamentInitView() + + NavigationLink(value: Screen.broadcast) { + Text("Diffusion") + } + case .build: TournamentRunningView(tournament: tournament) + + if tournament.hasEnded() { + NavigationLink(value: Screen.rankings) { + Text("Classement") + } + } } } } @@ -87,6 +98,8 @@ struct TournamentView: View { TournamentCallView(tournament: tournament) case .rankings: TournamentRankView() + case .broadcast: + BroadcastView() } } .environment(tournament) @@ -123,6 +136,9 @@ struct TournamentView: View { NavigationLink(value: Screen.rankings) { Text("Classement") } + NavigationLink(value: Screen.broadcast) { + Text("Diffusion") + } } } label: { LabelOptions()