From 4fece3c67eaacb99ac5cbac724ed57106f78e3df Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sat, 1 Jun 2024 00:19:21 +0200 Subject: [PATCH] fix lag --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/GroupStage.swift | 42 ++- PadelClub/Data/Match.swift | 42 ++- PadelClub/Data/Round.swift | 18 +- PadelClub/Data/Tournament.swift | 69 ++-- .../Cashier/Event/EventCreationView.swift | 101 +++--- .../Views/Components/MatchListView.swift | 22 +- .../Views/GroupStage/GroupStageView.swift | 14 +- .../Views/GroupStage/GroupStagesView.swift | 16 +- PadelClub/Views/Match/MatchSetupView.swift | 8 +- PadelClub/Views/Match/MatchSummaryView.swift | 26 -- .../Navigation/Agenda/ActivityView.swift | 36 +- .../Navigation/Agenda/EventListView.swift | 6 +- PadelClub/Views/Navigation/MainView.swift | 2 +- .../Navigation/Ongoing/OngoingView.swift | 1 - PadelClub/Views/Round/RoundSettingsView.swift | 38 +- PadelClub/Views/Round/RoundView.swift | 204 +++++++---- PadelClub/Views/Round/RoundsView.swift | 2 +- .../Team/Components/TeamHeaderView.swift | 4 - .../Views/Tournament/FileImportView.swift | 69 ++-- .../Components/InscriptionInfoView.swift | 44 ++- .../Screen/InscriptionManagerView.swift | 325 ++++++++++++------ .../Tournament/TournamentBuildView.swift | 72 +++- 23 files changed, 742 insertions(+), 423 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7464d21..c10a008 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1935,7 +1935,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1973,7 +1973,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 9e97677..5faf3d1 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -79,7 +79,7 @@ class GroupStage: ModelObject, Storable { guard teams().count == size else { return false } let _matches = _matches() if _matches.isEmpty { return false } - return _matches.allSatisfy { $0.hasEnded() } + return _matches.anySatisfy { $0.hasEnded() == false } == false } func buildMatches() { @@ -178,20 +178,50 @@ class GroupStage: ModelObject, Storable { return _matches().first(where: { matchIndexes.contains($0.index) }) } - func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] { + func availableToStart(playedMatches: [Match], in runningMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func group stage availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) } func runningMatches(playedMatches: [Match]) -> [Match] { - playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) + } + + func asyncRunningMatches(playedMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) } - func readyMatches(playedMatches: [Match]) -> [Match] { - playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) + + func readyMatches(playedMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) } func finishedMatches(playedMatches: [Match]) -> [Match] { - playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func group stage finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() } private func _matchOrder() -> [Int] { diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 33f3b28..b4fb2d9 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -69,10 +69,10 @@ class Match: ModelObject, Storable { try Store.main.deleteDependencies(items: self.teamScores) } - func indexInRound() -> Int { + func indexInRound(in matches: [Match]? = nil) -> Int { if groupStage != nil { return index - } else if let index = roundObject?.playedMatches().sorted(by: \.index).firstIndex(where: { $0.id == id }) { + } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) { return index } return RoundRule.matchIndexWithinRound(fromMatchIndex: index) @@ -86,16 +86,16 @@ class Match: ModelObject, Storable { [roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n") } - func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String { + func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String { if let groupStageObject { return groupStageObject.localizedMatchUpLabel(for: index) } switch displayStyle { case .wide: - return "Match \(indexInRound() + 1)" + return "Match \(indexInRound(in: matches) + 1)" case .short: - return "#\(indexInRound() + 1)" + return "#\(indexInRound(in: matches) + 1)" } } @@ -627,7 +627,7 @@ class Match: ModelObject, Storable { } func hasEnded() -> Bool { - endDate != nil || hasWalkoutTeam() || winningTeamId != nil + endDate != nil } func isGroupStage() -> Bool { @@ -705,6 +705,11 @@ class Match: ModelObject, Storable { } func team(_ team: TeamPosition) -> TeamRegistration? { +// let start = Date() +// defer { +// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) +// print("func match get team", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +// } if groupStage != nil { switch team { case .one: @@ -721,7 +726,30 @@ class Match: ModelObject, Storable { } } } - + + func asyncTeam(_ team: TeamPosition) async -> TeamRegistration? { +// let start = Date() +// defer { +// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) +// print("func match get team", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +// } + if groupStage != nil { + switch team { + case .one: + return groupStageProjectedTeam(.one) + case .two: + return groupStageProjectedTeam(.two) + } + } else { + switch team { + case .one: + return roundProjectedTeam(.one) + case .two: + return roundProjectedTeam(.two) + } + } + } + func teamNames(_ team: TeamRegistration?) -> [String]? { team?.players().map { $0.playerLabel() } } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 7373407..3d559e6 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -56,7 +56,7 @@ class Round: ModelObject, Storable { } func hasEnded() -> Bool { - playedMatches().allSatisfy({ $0.hasEnded() }) + playedMatches().anySatisfy({ $0.hasEnded() == false }) == false } func upperMatches(ofMatch match: Match) -> [Match] { @@ -221,10 +221,15 @@ class Round: ModelObject, Storable { } func playedMatches() -> [Match] { +// let start = Date() +// defer { +// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) +// print("func round playedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +// } if parent == nil { - enabledMatches() + return enabledMatches() } else { - _matches() + return _matches() } } @@ -398,9 +403,10 @@ class Round: ModelObject, Storable { } func roundStatus() -> String { - if hasStarted() && hasEnded() == false { + let hasEnded = hasEnded() + if hasStarted() && hasEnded == false { return "en cours" - } else if hasEnded() { + } else if hasEnded { return "terminée" } else { return "à démarrer" @@ -548,7 +554,7 @@ extension Round: Selectable { if let parentRound { return "Tour #\(parentRound.loserRounds().count - index)" } else { - return roundTitle() + return roundTitle(.short) } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index b623322..1459592 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -538,11 +538,11 @@ class Tournament : ModelObject, Storable { } func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] { - getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 0 } ?? [] + getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? [] } func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { - getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 1 } ?? [] + getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] } func availableSeedGroups() -> [SeedInterval] { @@ -615,7 +615,7 @@ class Tournament : ModelObject, Storable { if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count { return availableSeedGroup - } else if (availableSeeds.count == availableSeedOpponentSpot.count && availableSeeds.count == self.availableSeeds().count) && availableSeedGroup.count == availableSeedOpponentSpot.count { + } else if availableSeeds.count == availableSeedOpponentSpot.count && availableSeedGroup.count == availableSeedOpponentSpot.count { return availableSeedGroup } else if let chunks = availableSeedGroup.chunks() { if let chunk = chunks.first(where: { seedInterval in @@ -727,7 +727,12 @@ class Tournament : ModelObject, Storable { } func selectedSortedTeams() -> [TeamRegistration] { - //let start = Date() +// let start = Date() +// defer { +// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) +// print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +// } + var _sortedTeams : [TeamRegistration] = [] let _teams = unsortedTeams().filter({ $0.walkOut == false }) @@ -757,9 +762,6 @@ class Tournament : ModelObject, Storable { let groupStageTeams = Set(_completeTeams).subtracting(bracketTeams).sorted(using: defaultSorting, order: .ascending).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending) } - - //let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - //print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) return _sortedTeams } @@ -817,10 +819,6 @@ class Tournament : ModelObject, Storable { unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) } - func femalePlayers() -> [PlayerRegistration] { - unsortedPlayers().filter({ $0.isMalePlayer() == false }) - } - func unrankValue(for malePlayer: Bool) -> Int? { switch tournamentCategory { case .men: @@ -930,7 +928,7 @@ class Tournament : ModelObject, Storable { } } - func registrationIssues() -> Int { + func registrationIssues() async -> Int { let players : [PlayerRegistration] = unsortedPlayers() let selectedTeams : [TeamRegistration] = selectedSortedTeams() let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } @@ -957,19 +955,48 @@ class Tournament : ModelObject, Storable { return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) } - func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) -> [Match] { + func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting) } + func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting) + } + func runningMatches(_ allMatches: [Match]) -> [Match] { - allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting) + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting) } - func readyMatches(_ allMatches: [Match]) -> [Match] { + func readyMatches(_ allMatches: [Match]) async -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) } func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } let _limit = limit ?? courtCount return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) } @@ -1181,7 +1208,7 @@ class Tournament : ModelObject, Storable { } typealias TournamentStatus = (label:String, completion: String) - func cashierStatus() -> TournamentStatus { + func cashierStatus() async -> TournamentStatus { let selectedPlayers = selectedPlayers() let paid = selectedPlayers.filter({ $0.hasPaid() }) let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" @@ -1190,7 +1217,7 @@ class Tournament : ModelObject, Storable { return TournamentStatus(label: label, completion: completionLabel) } - func scheduleStatus() -> TournamentStatus { + func scheduleStatus() async -> TournamentStatus { let allMatches = allMatches() let ready = allMatches.filter({ $0.startDate != nil }) let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés" @@ -1199,7 +1226,7 @@ class Tournament : ModelObject, Storable { return TournamentStatus(label: label, completion: completionLabel) } - func callStatus() -> TournamentStatus { + func callStatus() async -> TournamentStatus { let selectedSortedTeams = selectedSortedTeams() let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false } let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " convoquées au bon horaire" @@ -1208,7 +1235,7 @@ class Tournament : ModelObject, Storable { return TournamentStatus(label: label, completion: completionLabel) } - func confirmedSummonStatus() -> TournamentStatus { + func confirmedSummonStatus() async -> TournamentStatus { let selectedSortedTeams = selectedSortedTeams() let called = selectedSortedTeams.filter { $0.confirmationDate != nil } let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées" @@ -1217,7 +1244,7 @@ class Tournament : ModelObject, Storable { return TournamentStatus(label: label, completion: completionLabel) } - func bracketStatus() -> String { + func bracketStatus() async -> String { let availableSeeds = availableSeeds() if availableSeeds.isEmpty == false { return "placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série" @@ -1233,7 +1260,7 @@ class Tournament : ModelObject, Storable { } } - func groupStageStatus() -> String { + func groupStageStatus() async -> String { let groupStageTeamsCount = groupStageTeams().count if groupStageTeamsCount == 0 || groupStageTeamsCount != teamsPerGroupStage * groupStageCount { return "à faire" diff --git a/PadelClub/Views/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index 6c959d1..3e04a56 100644 --- a/PadelClub/Views/Cashier/Event/EventCreationView.swift +++ b/PadelClub/Views/Cashier/Event/EventCreationView.swift @@ -90,39 +90,6 @@ struct EventCreationView: View { case .animation: animationEditorView } - - Section { - RowButtonView("Valider") { - let event = Event(creator: Store.main.userId, name: eventName) - event.club = selectedClub?.id - tournaments.forEach { tournament in - tournament.event = event.id - } - - do { - try dataStore.events.addOrUpdate(instance: event) - } catch { - Logger.error(error) - } - - tournaments.forEach { tournament in - tournament.courtCount = selectedClub?.courtCount ?? 2 - tournament.startDate = startingDate - tournament.dayDuration = duration - tournament.setupFederalSettings() - } - - do { - try dataStore.tournaments.addOrUpdate(contentOfs: tournaments) - } catch { - Logger.error(error) - } - - dismiss() - navigation.path.append(tournaments.first!) - } - .disabled(tournaments.isEmpty) - } } .toolbar { if textFieldIsFocus { @@ -144,11 +111,10 @@ struct EventCreationView: View { } ToolbarItem(placement: .topBarTrailing) { - BarButtonView("Ajouter une épreuve", icon: "plus.circle.fill") { - let tournament = Tournament.newEmptyInstance() - self.tournaments.append(tournament) + ButtonValidateView { + _validate() } - .popoverTip(multiTournamentsEventTip) + .disabled(tournaments.isEmpty) } } .navigationTitle("Nouvel événement") @@ -162,24 +128,63 @@ struct EventCreationView: View { } } + private func _validate() { + let event = Event(creator: Store.main.userId, name: eventName) + event.club = selectedClub?.id + tournaments.forEach { tournament in + tournament.event = event.id + } + + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } + + tournaments.forEach { tournament in + tournament.courtCount = selectedClub?.courtCount ?? 2 + tournament.startDate = startingDate + tournament.dayDuration = duration + tournament.setupFederalSettings() + } + + do { + try dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + } catch { + Logger.error(error) + } + + dismiss() + navigation.path.append(tournaments.first!) + } + @ViewBuilder private var approvedTournamentEditorView: some View { - ForEach(tournaments) { tournament in + ForEach(tournaments.indices, id: \.self) { index in + let tournament = tournaments[index] Section { TournamentConfigurationView(tournament: tournament) - } footer: { + } header: { if tournaments.count > 1 { - FooterButtonView("effacer") { - tournaments.removeAll(where: { $0 == tournament }) + HStack { + Spacer() + FooterButtonView("effacer") { + tournaments.removeAll(where: { $0 == tournament }) + } + .textCase(nil) + } + } + } footer: { + if index == tournaments.count - 1 { + HStack { + Spacer() + FooterButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") { + let tournament = Tournament.newEmptyInstance() + self.tournaments.append(tournament) + } + .popoverTip(multiTournamentsEventTip) } } - } - } - - Section { - RowButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") { - let tournament = Tournament.newEmptyInstance() - self.tournaments.append(tournament) } } } diff --git a/PadelClub/Views/Components/MatchListView.swift b/PadelClub/Views/Components/MatchListView.swift index 8ea90a7..90dd9d9 100644 --- a/PadelClub/Views/Components/MatchListView.swift +++ b/PadelClub/Views/Components/MatchListView.swift @@ -10,26 +10,30 @@ import SwiftUI struct MatchListView: View { @EnvironmentObject var dataStore: DataStore let section: String - let matches: [Match] + let matches: [Match]? var matchViewStyle: MatchViewStyle = .standardStyle @State var isExpanded: Bool = true @ViewBuilder var body: some View { - if matches.isEmpty == false { - Section { - DisclosureGroup(isExpanded: $isExpanded) { + Section { + DisclosureGroup(isExpanded: $isExpanded) { + if let matches { ForEach(matches) { match in MatchRowView(match: match, matchViewStyle: matchViewStyle) .listRowInsets(EdgeInsets(top: 0, leading: -2, bottom: 0, trailing: 8)) } - } label: { - LabeledContent { - Text(matches.count.formatted() + " match" + matches.count.pluralSuffix) - } label: { - Text(section.firstCapitalized) + } + } label: { + LabeledContent { + if matches == nil { + ProgressView() + } else { + Text(matches!.count.formatted() + " match" + matches!.count.pluralSuffix) } + } label: { + Text(section.firstCapitalized) } } } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 1631bae..1ddb5eb 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -17,6 +17,10 @@ struct GroupStageView: View { @State private var confirmResetMatch: Bool = false let playedMatches: [Match] + @State private var runningMatches: [Match]? + @State private var readyMatches: [Match]? + @State private var availableToStart: [Match]? + init(groupStage: GroupStage) { self.groupStage = groupStage self.playedMatches = groupStage.playedMatches() @@ -44,12 +48,16 @@ struct GroupStageView: View { } .headerProminence(.increased) - let runningMatches = groupStage.runningMatches(playedMatches: playedMatches) - MatchListView(section: "disponible", matches: groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches)) MatchListView(section: "en cours", matches: runningMatches) - MatchListView(section: "à lancer", matches: groupStage.readyMatches(playedMatches: playedMatches)) + MatchListView(section: "prêt à démarrer", matches: availableToStart) + MatchListView(section: "à lancer", matches: self.readyMatches) MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false) } + .task { + self.runningMatches = await groupStage.asyncRunningMatches(playedMatches: playedMatches) + self.readyMatches = await groupStage.readyMatches(playedMatches: playedMatches) + self.availableToStart = await groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches ?? []) + } .toolbar { ToolbarItem(placement: .topBarTrailing) { _groupStageMenuView() diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 28753ac..09b14bf 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -76,24 +76,30 @@ struct GroupStagesView: View { return allDestinations } + @State private var runningMatches: [Match]? + @State private var readyMatches: [Match]? + @State private var availableToStart: [Match]? + var body: some View { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) switch selectedDestination { case .all: - let runningMatches = tournament.runningMatches(allMatches) - let availableToStart = tournament.availableToStart(allMatches, in: runningMatches) - let readyMatches = tournament.readyMatches(allMatches) let finishedMatches = tournament.finishedMatches(allMatches) List { MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) - MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) + MatchListView(section: "prêt à démarrer", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false) } + .task { + runningMatches = await tournament.asyncRunningMatches(allMatches) + availableToStart = await tournament.availableToStart(allMatches, in: runningMatches ?? []) + readyMatches = await tournament.readyMatches(allMatches) + } .overlay { - if availableToStart.isEmpty && runningMatches.isEmpty && readyMatches.isEmpty && finishedMatches.isEmpty { + if availableToStart?.isEmpty == true && runningMatches?.isEmpty == true && readyMatches?.isEmpty == true && finishedMatches.isEmpty == true { ContentUnavailableView("Aucun match à afficher", systemImage: "tennisball") } } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index b45777a..11f1410 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -22,8 +22,11 @@ struct MatchSetupView: View { @ViewBuilder func _teamView(inTeamPosition teamPosition: TeamPosition) -> some View { - let team = match.team(teamPosition) - let teamScore = match.teamScore(ofTeam: team) + let scores = match.teamScores + let team = scores.isEmpty ? nil : match.team(teamPosition) + let teamScore = (team != nil) ? scores.first(where: { $0.teamRegistration == team!.id }) : nil + let walkOutSpot = teamScore?.walkOut == 1 + if let team, teamScore?.walkOut == nil { VStack(alignment: .leading, spacing: 0) { if let teamScore, teamScore.luckyLoser != nil { @@ -67,7 +70,6 @@ struct MatchSetupView: View { .strikethrough() } HStack { - let walkOutSpot = match.isWalkOutSpot(teamPosition) let luckyLosers = walkOutSpot ? match.luckyLosers() : [] TeamPickerView(luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index 5228dd3..8efbdf4 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -44,32 +44,6 @@ struct MatchSummaryView: View { } var body: some View { - matchSummaryView -// .contextMenu { -// ForEach(match.teamScores) { entrant in -// if let team = entrant.team, team.orderedPlayers.count > 2 { -// NavigationLink { -// PlayerPickerView(match: match, team: team) -// } label: { -// if let teamTitle = team.entrant?.brand?.title { -// Text(teamTitle).foregroundStyle(.secondary) -// } else { -// let index = match.orderedEntrants.firstIndex(where: { $0 == entrant }) ?? 0 -// Text("Équipe \(index + 1)") -// } -// if match.players(from: team).isEmpty { -// Text("Choisir la paire") -// } else { -// Text("Modifier la paire") -// } -// } -// } -// } -// } - } - - @ViewBuilder - var matchSummaryView: some View { VStack(alignment: .leading) { if matchViewStyle != .plainStyle { if matchViewStyle == .feedStyle, let tournament = match.currentTournament() { diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 1539b9b..bb07f63 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -25,6 +25,7 @@ struct ActivityView: View { var runningTournaments: [FederalTournamentHolder] { dataStore.tournaments.filter({ $0.endDate == nil }) .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) + .sorted(using: SortDescriptor(\.startDate)) } var endedTournaments: [Tournament] { @@ -32,16 +33,16 @@ struct ActivityView: View { .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) .sorted(using: SortDescriptor(\.startDate, order: .reverse)) } - - func _activityStatus() -> String? { - let tournaments = tournaments - if tournaments.isEmpty && federalDataViewModel.areFiltersEnabled() == false { - return nil - } else { - let count = tournaments.map { $0.tournaments.count }.reduce(0,+) - return "\(count) tournoi" + count.pluralSuffix - } - } +// +// func _activityStatus() -> String? { +// let tournaments = tournaments +// if tournaments.isEmpty && federalDataViewModel.areFiltersEnabled() == false { +// return nil +// } else { +// let count = tournaments.map { $0.tournaments.count }.reduce(0,+) +// return "\(count) tournoi" + count.pluralSuffix +// } +// } var tournaments: [FederalTournamentHolder] { switch navigation.agendaDestination! { @@ -136,19 +137,10 @@ struct ActivityView: View { } .toolbar { if presentToolbar { - let _activityStatus = _activityStatus() - if federalDataViewModel.areFiltersEnabled() || _activityStatus != nil { + //let _activityStatus = _activityStatus() + if federalDataViewModel.areFiltersEnabled() { ToolbarItem(placement: .status) { - VStack(spacing: -2) { - if federalDataViewModel.areFiltersEnabled() { - Text(federalDataViewModel.filterStatus()) - } - if let _activityStatus { - Text(_activityStatus) - .foregroundStyle(.secondary) - } - } - .font(.footnote) + Text(federalDataViewModel.filterStatus()) } } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index 3011b04..b5ea073 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -20,8 +20,8 @@ struct EventListView: View { let groupedTournamentsByDate = Dictionary(grouping: navigation.agendaDestination == .tenup ? federalDataViewModel.filteredFederalTournaments : tournaments) { $0.startDate.startOfMonth } switch viewStyle { case .list: - ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in - if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) { + ForEach(Array(groupedTournamentsByDate.keys), id: \.self) { section in + if let _tournaments = groupedTournamentsByDate[section] { Section { _listView(_tournaments) } header: { @@ -37,7 +37,7 @@ struct EventListView: View { } case .calendar: ForEach(_nextMonths(), id: \.self) { section in - let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) ?? [] + let _tournaments = groupedTournamentsByDate[section] ?? [] Section { CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) } header: { diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 3b4daa1..35506bd 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -48,7 +48,7 @@ struct MainView: View { dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }) } - var badgeText: Text? = Store.main.userName() == nil ? Text("!").font(.headline) : nil + var badgeText: Text? = Store.main.userId == nil ? Text("!").font(.headline) : nil var body: some View { TabView(selection: selectedTabHandler) { diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index 53785d0..4e4f865 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -17,7 +17,6 @@ struct OngoingView: View { var matches: [Match] { let sorting = sortByField ? fieldSorting : defaultSorting - let now = Date() return dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending) } diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index fbf9253..eccbf35 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -42,19 +42,7 @@ struct RoundSettingsView: View { // } Section { RowButtonView("Retirer toutes les têtes de séries", role: .destructive) { - tournament.unsortedTeams().forEach({ team in - tournament.resetTeamScores(in: team.bracketPosition) - team.bracketPosition = nil - }) - do { - try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) - } catch { - Logger.error(error) - } - tournament.allRounds().forEach({ round in - round.enableRound() - }) - self.isEditingTournamentSeed.wrappedValue = true + await _removeAllSeeds() } } @@ -94,6 +82,30 @@ struct RoundSettingsView: View { } } } + + private func _removeAllSeeds() async { + tournament.unsortedTeams().forEach({ team in + team.bracketPosition = nil + }) + let ts = tournament.allRoundMatches().flatMap { match in + match.teamScores + } + + do { + try DataStore.shared.teamScores.delete(contentOfs: ts) + } catch { + Logger.error(error) + } + do { + try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) + } catch { + Logger.error(error) + } + tournament.allRounds().forEach({ round in + round.enableRound() + }) + self.isEditingTournamentSeed.wrappedValue = true + } } #Preview { diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 742396b..753a01c 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -13,6 +13,27 @@ struct RoundView: View { @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore @State private var selectedSeedGroup: SeedInterval? + @State private var spaceLeft: [Match] = [] + @State private var seedSpaceLeft: [Match] = [] + @State private var availableSeedGroup: SeedInterval? + + private func _getAvailableSeedGroup() async { + availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) + } + + private func _getSpaceLeft() async { + spaceLeft.removeAll() + seedSpaceLeft.removeAll() + round.displayableMatches().forEach({ + let count = $0.teamScores.count + if count == 0 { + seedSpaceLeft.append($0) + } else if count == 1 { + spaceLeft.append($0) + } + }) + } + var showVisualDrawView: Binding { Binding( get: { selectedSeedGroup != nil }, set: { @@ -26,14 +47,13 @@ struct RoundView: View { var body: some View { List { - - let loserRounds = round.loserRounds() - let availableSeeds = tournament.availableSeeds() - let availableQualifiedTeams = tournament.availableQualifiedTeams() let displayableMatches = round.displayableMatches().sorted(by: \.index) - let spaceLeft = displayableMatches.filter({ $0.hasSpaceLeft() }) - let seedSpaceLeft = displayableMatches.filter({ $0.isEmpty() }) - if isEditingTournamentSeed.wrappedValue == false { + let loserRounds = round.loserRounds() + if displayableMatches.isEmpty { + Section { + ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball") + } + } else if isEditingTournamentSeed.wrappedValue == false { //(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue }) if loserRounds.isEmpty == false { let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() @@ -48,14 +68,19 @@ struct RoundView: View { } } } else { - if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) { + let availableSeeds = tournament.availableSeeds() + let availableQualifiedTeams = tournament.availableQualifiedTeams() + + if availableSeeds.isEmpty == false, let availableSeedGroup { Section { RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) - _save() + //_save() if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { self.isEditingTournamentSeed.wrappedValue = false } + await _getSpaceLeft() + await _getAvailableSeedGroup() } } footer: { if availableSeedGroup.isFixed() == false { @@ -74,86 +99,95 @@ struct RoundView: View { } } - - if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false { - Section { - DisclosureGroup { - ForEach(availableQualifiedTeams) { team in - NavigationLink { - SpinDrawView(drawees: [team], segments: spaceLeft) { results in - Task { - results.forEach { drawResult in - team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true) - } - _save() - if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + if availableQualifiedTeams.isEmpty == false { + if spaceLeft.isEmpty == false { + Section { + DisclosureGroup { + ForEach(availableQualifiedTeams) { team in + NavigationLink { + SpinDrawView(drawees: [team], segments: spaceLeft) { results in + Task { + results.forEach { drawResult in + team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true) + } + await _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } + await _getSpaceLeft() + await _getAvailableSeedGroup() } } + } label: { + TeamRowView(team: team, displayCallDate: false) } - } label: { - TeamRowView(team: team, displayCallDate: false) } + } label: { + Text("Qualifié\(availableQualifiedTeams.count.pluralSuffix) à placer").badge(availableQualifiedTeams.count) } - } label: { - Text("Qualifié\(availableQualifiedTeams.count.pluralSuffix) à placer").badge(availableQualifiedTeams.count) + } header: { + Text("Tirage au sort visuel d'un qualifié").font(.subheadline) } - } header: { - Text("Tirage au sort visuel d'un qualifié").font(.subheadline) } } - if availableSeeds.isEmpty == false && seedSpaceLeft.isEmpty == false { - Section { - DisclosureGroup { - ForEach(availableSeeds) { team in - NavigationLink { - SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in - Task { - results.forEach { drawResult in - team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) - } - _save() - if availableSeeds.isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + if availableSeeds.isEmpty == false { + if seedSpaceLeft.isEmpty == false { + Section { + DisclosureGroup { + ForEach(availableSeeds) { team in + NavigationLink { + SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in + Task { + results.forEach { drawResult in + team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) + } + await _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } + await _getSpaceLeft() + await _getAvailableSeedGroup() } } + } label: { + TeamRowView(team: team, displayCallDate: false) } - } label: { - TeamRowView(team: team, displayCallDate: false) } + } label: { + Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) } - } label: { - Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) + } header: { + Text("Tirage au sort visuel d'une tête de série").font(.subheadline) } - } header: { - Text("Tirage au sort visuel d'une tête de série").font(.subheadline) - } - } else if availableSeeds.isEmpty == false && spaceLeft.isEmpty == false { - Section { - DisclosureGroup { - ForEach(availableSeeds) { team in - NavigationLink { - SpinDrawView(drawees: [team], segments: spaceLeft) { results in - Task { - results.forEach { drawResult in - team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) - } - _save() - if availableSeeds.isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + } else if spaceLeft.isEmpty == false { + Section { + DisclosureGroup { + ForEach(availableSeeds) { team in + NavigationLink { + SpinDrawView(drawees: [team], segments: spaceLeft) { results in + Task { + results.forEach { drawResult in + team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) + } + await _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } + await _getSpaceLeft() + await _getAvailableSeedGroup() } } + } label: { + TeamRowView(team: team, displayCallDate: false) } - } label: { - TeamRowView(team: team, displayCallDate: false) } + } label: { + Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) } - } label: { - Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) + } header: { + Text("Tirage au sort visuel d'une tête de série").font(.subheadline) } - } header: { - Text("Tirage au sort visuel d'une tête de série").font(.subheadline) } } } @@ -164,19 +198,30 @@ struct RoundView: View { HStack { Text(round.roundTitle(.wide)) if round.index > 0 { - Text(match.matchTitle(.short)) + Text(match.matchTitle(.short, inMatches: displayableMatches)) } else { let tournamentTeamCount = tournament.teamCount - if let seedIntervalPointRange = loserRounds.first?.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + if let seedIntervalPointRange = round.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { Spacer() Text(seedIntervalPointRange) .font(.caption) } } + + #if DEBUG + Spacer() + + Text(match.teamScores.count.formatted()) + #endif } } } } + .onAppear { + Task { + await _prepareRound() + } + } .fullScreenCover(isPresented: showVisualDrawView) { if let availableSeedGroup = selectedSeedGroup { let seeds = tournament.seeds(inSeedGroup: availableSeedGroup) @@ -187,7 +232,7 @@ struct RoundView: View { draws.forEach { drawResult in seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) } - _save() + await _save() if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { self.isEditingTournamentSeed.wrappedValue = false } @@ -201,7 +246,9 @@ struct RoundView: View { ToolbarItem(placement: .topBarTrailing) { Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { if isEditingTournamentSeed.wrappedValue { - _save() + Task { + await _save() + } } isEditingTournamentSeed.wrappedValue.toggle() } @@ -209,7 +256,7 @@ struct RoundView: View { } } - private func _save() { + private func _save() async { do { try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) } catch { @@ -221,7 +268,7 @@ struct RoundView: View { rounds.forEach { round in let matches = round.playedMatches() matches.forEach { match in - match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound()) + match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound(in: matches)) } } let allRoundMatches = tournament.allRoundMatches() @@ -231,6 +278,15 @@ struct RoundView: View { Logger.error(error) } } + + private func _prepareRound() async { + Task { + await _getSpaceLeft() + } + Task { + await _getAvailableSeedGroup() + } + } } #Preview { diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift index eac0d07..e1fbacc 100644 --- a/PadelClub/Views/Round/RoundsView.swift +++ b/PadelClub/Views/Round/RoundsView.swift @@ -33,7 +33,7 @@ struct RoundsView: View { RoundSettingsView() .navigationTitle("Réglages") case .some(let selectedRound): - RoundView(round: selectedRound) + RoundView(round: selectedRound).id(selectedRound.id) .navigationTitle(selectedRound.roundTitle()) } } diff --git a/PadelClub/Views/Team/Components/TeamHeaderView.swift b/PadelClub/Views/Team/Components/TeamHeaderView.swift index d6a0dbf..32dfb0f 100644 --- a/PadelClub/Views/Team/Components/TeamHeaderView.swift +++ b/PadelClub/Views/Team/Components/TeamHeaderView.swift @@ -13,10 +13,6 @@ struct TeamHeaderView: View { var tournament: Tournament? var body: some View { - _teamHeaderView(team, teamIndex: teamIndex) - } - - private func _teamHeaderView(_ team: TeamRegistration, teamIndex: Int?) -> some View { HStack(spacing: 16.0) { if let teamIndex { VStack(alignment: .leading, spacing: 0) { diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 530384f..5eb7799 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -27,7 +27,8 @@ struct FileImportView: View { @State private var selectedOptions: Set = Set() @State private var fileProvider: FileImportManager.FileProvider = .frenchFederation - + @State private var validationInProgress: Bool = false + private var filteredTeams: [FileImportManager.TeamHolder] { return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight) } @@ -104,6 +105,16 @@ struct FileImportView: View { } } + if validationInProgress { + Section { + LabeledContent { + ProgressView() + } label: { + Text("Mise à jour des équipes") + } + } + } + if let errorMessage { Section { Text(errorMessage) @@ -146,7 +157,7 @@ struct FileImportView: View { Section { ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash") } - } else if didImport { + } else if didImport && validationInProgress == false { let _filteredTeams = filteredTeams let previousTeams = tournament.sortedTeams() @@ -222,38 +233,40 @@ struct FileImportView: View { ToolbarItem(placement: .topBarTrailing) { ButtonValidateView { -// if false { //selectedOptions.contains(.deleteBeforeImport) -// try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams()) -// } - - if true { //selectedOptions.contains(.notFoundAreWalkOut) - let previousTeams = filteredTeams.compactMap({ $0.previousTeam }) - - let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams)) - unfound.forEach { team in - team.resetPositions() - team.wildCardBracket = false - team.wildCardGroupStage = false - team.walkOut = true - } - - do { - try dataStore.teamRegistrations.addOrUpdate(contentOfs: unfound) - } catch { - Logger.error(error) - } - - } - - tournament.importTeams(filteredTeams) - dismiss() + _validate() } .disabled(teams.isEmpty) } } + .interactiveDismissDisabled(validationInProgress) + .disabled(validationInProgress) + } + + private func _validate() { + validationInProgress = true + Task { + let previousTeams = filteredTeams.compactMap({ $0.previousTeam }) + + let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams)) + unfound.forEach { team in + team.resetPositions() + team.wildCardBracket = false + team.wildCardGroupStage = false + team.walkOut = true + } + + do { + try dataStore.teamRegistrations.addOrUpdate(contentOfs: unfound) + } catch { + Logger.error(error) + } + + tournament.importTeams(filteredTeams) + dismiss() + } } - func _startImport(fileContent: String) async throws { + private func _startImport(fileContent: String) async throws { await MainActor.run { errorMessage = nil teams.removeAll() diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 9541109..bc0710e 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -11,20 +11,16 @@ struct InscriptionInfoView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament - var players : [PlayerRegistration] { tournament.unsortedPlayers() } - var selectedTeams : [TeamRegistration] { tournament.selectedSortedTeams() } - - var callDateIssue : [TeamRegistration] { - selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } - } - - var waitingList : [TeamRegistration] { tournament.waitingListTeams(in: selectedTeams) } - var duplicates : [PlayerRegistration] { tournament.duplicates(in: players) } - var problematicPlayers : [PlayerRegistration] { players.filter({ $0.sex == nil }) } - var inadequatePlayers : [PlayerRegistration] { tournament.inadequatePlayers(in: players) } - var playersWithoutValidLicense : [PlayerRegistration] { tournament.playersWithoutValidLicense(in: players) } - var entriesFromBeachPadel : [TeamRegistration] { tournament.unsortedTeams().filter({ $0.isImported() }) } - var playersMissing : [TeamRegistration] { selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) } + @State private var players : [PlayerRegistration] = [] + @State private var selectedTeams : [TeamRegistration] = [] + @State private var callDateIssue : [TeamRegistration] = [] + @State private var waitingList : [TeamRegistration] = [] + @State private var duplicates : [PlayerRegistration] = [] + @State private var problematicPlayers : [PlayerRegistration] = [] + @State private var inadequatePlayers : [PlayerRegistration] = [] + @State private var playersWithoutValidLicense : [PlayerRegistration] = [] + @State private var entriesFromBeachPadel : [TeamRegistration] = [] + @State private var playersMissing : [TeamRegistration] = [] var body: some View { List { @@ -196,10 +192,28 @@ struct InscriptionInfoView: View { .listRowView(color: .pink) } } + .task { + await _getIssues() + } .navigationTitle("Synthèse") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - } + } + + private func _getIssues() async { + Task { + players = tournament.unsortedPlayers() + selectedTeams = tournament.selectedSortedTeams() + callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } + waitingList = tournament.waitingListTeams(in: selectedTeams) + duplicates = tournament.duplicates(in: players) + problematicPlayers = players.filter({ $0.sex == nil }) + inadequatePlayers = tournament.inadequatePlayers(in: players) + playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players) + entriesFromBeachPadel = tournament.unsortedTeams().filter({ $0.isImported() }) + playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) + } + } } #Preview { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 2f5f3fe..f9e196c 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -9,12 +9,22 @@ import SwiftUI import TipKit import LeStorage +let slideToDeleteTip = SlideToDeleteTip() +let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() +let fileTip = InscriptionManagerFileInputTip() +let pasteTip = InscriptionManagerPasteInputTip() +let searchTip = InscriptionManagerSearchInputTip() +let createTip = InscriptionManagerCreateInputTip() +let rankUpdateTip = InscriptionManagerRankUpdateTip() +let padelBeachExportTip = PadelBeachExportTip() +let padelBeachImportTip = PadelBeachImportTip() + struct InscriptionManagerView: View { @EnvironmentObject var dataStore: DataStore @EnvironmentObject var networkMonitor: NetworkMonitor @FetchRequest( - sortDescriptors: [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)], + sortDescriptors: [], animation: .default) private var fetchPlayers: FetchedResults @@ -41,7 +51,14 @@ struct InscriptionManagerView: View { @State private var contactType: ContactType? = nil @State private var sentError: ContactManagerError? = nil @State private var showSubscriptionView: Bool = false - + @State private var registrationIssues: Int? = nil + @State private var sortedTeams: [TeamRegistration] = [] + @State private var unfilteredTeams: [TeamRegistration] = [] + @State private var walkoutTeams: [TeamRegistration] = [] + @State private var unsortedTeamsWithoutWO: [TeamRegistration] = [] + @State private var unsortedPlayers: [PlayerRegistration] = [] + @State private var teamPaste: URL? + var messageSentFailed: Binding { Binding { sentError != nil @@ -83,20 +100,9 @@ struct InscriptionManagerView: View { } } - let slideToDeleteTip = SlideToDeleteTip() - let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() - let fileTip = InscriptionManagerFileInputTip() - let pasteTip = InscriptionManagerPasteInputTip() - let searchTip = InscriptionManagerSearchInputTip() - let createTip = InscriptionManagerCreateInputTip() - let rankUpdateTip = InscriptionManagerRankUpdateTip() - let padelBeachExportTip = PadelBeachExportTip() - let padelBeachImportTip = PadelBeachImportTip() - let categoryOption: PlayerFilterOption let filterable: Bool - let dates = Set(SourceFileManager.shared.allFilesSortedByDate(true).map({ $0.dateFromPath })).sorted().reversed() - + init(tournament: Tournament) { self.tournament = tournament _currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate) @@ -110,6 +116,16 @@ struct InscriptionManagerView: View { } } + private func _clearScreen() { + teamPaste = nil + unsortedPlayers.removeAll() + unfilteredTeams.removeAll() + walkoutTeams.removeAll() + unsortedTeamsWithoutWO.removeAll() + sortedTeams.removeAll() + registrationIssues = nil + } + // Function to create a simple hash from a list of IDs private func _simpleHash(ids: [String]) -> Int { // Combine the hash values of each string @@ -121,7 +137,43 @@ struct InscriptionManagerView: View { return _simpleHash(ids: ids1) != _simpleHash(ids: ids2) } + private func _setHash() async { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _setHash", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + let selectedSortedTeams = tournament.selectedSortedTeams() + if self.teamsHash == nil, selectedSortedTeams.isEmpty == false { + self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) + } + } + + private func _handleHashDiff() async { + let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) + if let teamsHash, newHash != teamsHash { + self.teamsHash = newHash + if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false { + self.tournament.shouldVerifyBracket = true + self.tournament.shouldVerifyGroupStage = true + + let waitingList = self.tournament.waitingListTeams(in: self.tournament.selectedSortedTeams()) + waitingList.forEach { team in + if team.bracketPosition != nil || team.groupStagePosition != nil { + team.resetPositions() + } + } + + do { + try dataStore.teamRegistrations.addOrUpdate(contentOfs: waitingList) + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } + } + } var body: some View { VStack(spacing: 0) { @@ -130,38 +182,16 @@ struct InscriptionManagerView: View { _buildingTeamView() } else if tournament.unsortedTeams().isEmpty { _inscriptionTipsView() - } else { - _teamRegisteredView() } + _teamRegisteredView() } .onAppear { - let selectedSortedTeams = tournament.selectedSortedTeams() - if self.teamsHash == nil, selectedSortedTeams.isEmpty == false { - self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) - } + _getTeams() } .onDisappear { - let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) - if let teamsHash, newHash != teamsHash { - self.teamsHash = newHash - if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false { - self.tournament.shouldVerifyBracket = true - self.tournament.shouldVerifyGroupStage = true - - let waitingList = self.tournament.waitingListTeams(in: self.tournament.selectedSortedTeams()) - waitingList.forEach { team in - if team.bracketPosition != nil || team.groupStagePosition != nil { - team.resetPositions() - } - } - - do { - try dataStore.teamRegistrations.addOrUpdate(contentOfs: waitingList) - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } - } + Task { + await _handleHashDiff() + } } .alert("Un problème est survenu", isPresented: messageSentFailed) { @@ -258,10 +288,14 @@ struct InscriptionManagerView: View { .tint(.master) } .onChange(of: tournament.prioritizeClubMembers) { + _clearScreen() _save() + _getTeams() } .onChange(of: tournament.teamSorting) { + _clearScreen() _save() + _getTeams() } .onChange(of: currentRankSourceDate) { if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate { @@ -334,8 +368,10 @@ struct InscriptionManagerView: View { Label("Clôturer", systemImage: "lock") } Divider() - ShareLink(item: tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))) { - Label("Exporter les paires", systemImage: "square.and.arrow.up") + if let teamPaste { + ShareLink(item: teamPaste) { + Label("Exporter les paires", systemImage: "square.and.arrow.up") + } } Button { presentImportView = true @@ -373,7 +409,28 @@ struct InscriptionManagerView: View { createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil } - private func _getTeams(from sortedTeams: [TeamRegistration]) -> [TeamRegistration] { + private func _prepareStats() async { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _prepareStats", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + + unsortedPlayers = tournament.unsortedPlayers() + walkoutTeams = tournament.walkoutTeams() + unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() + teamPaste = tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short)) + } + + private func _prepareTeams() { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + + sortedTeams = tournament.sortedTeams() + var teams = sortedTeams if filterMode == .walkOut { teams = teams.filter({ $0.walkOut }) @@ -384,17 +441,32 @@ struct InscriptionManagerView: View { } if byDecreasingOrdering { - return teams.reversed() + self.unfilteredTeams = teams.reversed() } else { - return teams + self.unfilteredTeams = teams + } + } + + private func _getIssues() async { + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func _getIssues", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + await registrationIssues = tournament.registrationIssues() + } + + private func _getTeams() { + _prepareTeams() + Task { + await _prepareStats() + await _getIssues() + await _setHash() } } private func _teamRegisteredView() -> some View { List { - let sortedTeams = tournament.sortedTeams() - let unfilteredTeams = _getTeams(from: sortedTeams) - if presentSearch == false { _rankHandlerView() _relatedTips() @@ -418,6 +490,7 @@ struct InscriptionManagerView: View { Task { await MainActor.run() { fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) + fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] pasteString = searchField } } @@ -474,6 +547,7 @@ struct InscriptionManagerView: View { Task { await MainActor.run { fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) + fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] pasteString = first autoSelect = true } @@ -504,6 +578,8 @@ struct InscriptionManagerView: View { @ViewBuilder func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View { Section { + let dates = Set(SourceFileManager.shared.allFilesSortedByDate(true).map({ $0.dateFromPath })).sorted().reversed() + Picker(selection: $currentRankSourceDate) { if currentRankSourceDate == nil { Text("inconnu").tag(nil as Date?) @@ -571,6 +647,7 @@ struct InscriptionManagerView: View { Task { await MainActor.run { fetchPlayers.nsPredicate = _pastePredicate(pasteField: paste, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) + fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] pasteString = paste autoSelect = true } @@ -621,9 +698,6 @@ struct InscriptionManagerView: View { private func _informationView(count: Int) -> some View { Section { - let walkoutTeams = tournament.walkoutTeams() - let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() - LabeledContent { Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) } label: { @@ -647,7 +721,11 @@ struct InscriptionManagerView: View { .environment(tournament) } label: { LabeledContent { - Text(tournament.registrationIssues().formatted()).font(.largeTitle) + if let registrationIssues { + Text(registrationIssues.formatted()).font(.largeTitle) + } else { + ProgressView() + } } label: { Text("Problèmes détéctés") if let closedRegistrationDate = tournament.closedRegistrationDate { @@ -660,43 +738,43 @@ struct InscriptionManagerView: View { @ViewBuilder private func _relatedTips() -> some View { - if pasteString == nil - && createdPlayerIds.isEmpty - && tournament.unsortedTeams().count >= tournament.teamCount - && tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty { - Section { - TipView(padelBeachExportTip) { action in - if action.id == "more-info-export" { - isLearningMore = true - } - if action.id == "padel-beach" { - UIApplication.shared.open(URLs.beachPadel.url) - } - } - .tipStyle(tint: nil) - } - Section { - TipView(padelBeachImportTip) { action in - if action.id == "more-info-import" { - presentImportView = true - } - } - .tipStyle(tint: nil) - } - } - +// if pasteString == nil +// && createdPlayerIds.isEmpty +// && tournament.unsortedTeams().count >= tournament.teamCount +// && tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty { +// Section { +// TipView(padelBeachExportTip) { action in +// if action.id == "more-info-export" { +// isLearningMore = true +// } +// if action.id == "padel-beach" { +// UIApplication.shared.open(URLs.beachPadel.url) +// } +// } +// .tipStyle(tint: nil) +// } +// Section { +// TipView(padelBeachImportTip) { action in +// if action.id == "more-info-import" { +// presentImportView = true +// } +// } +// .tipStyle(tint: nil) +// } +// } +// - if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false { + if tournament.tournamentCategory == .men && unsortedPlayers.filter({ $0.isMalePlayer() == false }).isEmpty == false { Section { TipView(inscriptionManagerWomanRankTip) .tipStyle(tint: nil) } } - - Section { - TipView(slideToDeleteTip) - .tipStyle(tint: nil) - } +// +// Section { +// TipView(slideToDeleteTip) +// .tipStyle(tint: nil) +// } } private func _searchSource() -> String? { @@ -777,6 +855,9 @@ struct InscriptionManagerView: View { createdPlayers.removeAll() createdPlayerIds.removeAll() pasteString = nil + + _clearScreen() + _getTeams() } private func _updateTeam() { @@ -797,6 +878,8 @@ struct InscriptionManagerView: View { createdPlayerIds.removeAll() pasteString = nil self.editedTeam = nil + _clearScreen() + _getTeams() } private func _buildingTeamView() -> some View { @@ -999,14 +1082,19 @@ struct InscriptionManagerView: View { Toggle(isOn: .init(get: { return team.wildCardBracket }, set: { value in - team.resetPositions() - team.wildCardGroupStage = false - team.walkOut = false - team.wildCardBracket = value - do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) + _clearScreen() + + Task { + team.resetPositions() + team.wildCardGroupStage = false + team.walkOut = false + team.wildCardBracket = value + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _getTeams() } })) { Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle") @@ -1015,14 +1103,19 @@ struct InscriptionManagerView: View { Toggle(isOn: .init(get: { return team.wildCardGroupStage }, set: { value in - team.resetPositions() - team.wildCardBracket = false - team.walkOut = false - team.wildCardGroupStage = value - do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) + _clearScreen() + + Task { + team.resetPositions() + team.wildCardBracket = false + team.walkOut = false + team.wildCardGroupStage = value + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _getTeams() } })) { Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle") @@ -1032,24 +1125,32 @@ struct InscriptionManagerView: View { Toggle(isOn: .init(get: { return team.walkOut }, set: { value in - team.resetPositions() - team.wildCardBracket = false - team.wildCardGroupStage = false - team.walkOut = value - do { - try dataStore.teamRegistrations.addOrUpdate(instance: team) - } catch { - Logger.error(error) + _clearScreen() + Task { + team.resetPositions() + team.wildCardBracket = false + team.wildCardGroupStage = false + team.walkOut = value + do { + try dataStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + _getTeams() } })) { Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle") } Divider() Button(role: .destructive) { - do { - try dataStore.teamRegistrations.delete(instance: team) - } catch { - Logger.error(error) + _clearScreen() + Task { + do { + try dataStore.teamRegistrations.delete(instance: team) + } catch { + Logger.error(error) + } + _getTeams() } } label: { LabelDelete() diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 2c88f24..7db9a94 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -9,6 +9,11 @@ import SwiftUI struct TournamentBuildView: View { var tournament: Tournament + @State private var bracketStatus: String? + @State private var groupStageStatus: String? + @State private var callStatus: Tournament.TournamentStatus? + @State private var scheduleStatus: Tournament.TournamentStatus? + @State private var cashierStatus: Tournament.TournamentStatus? @ViewBuilder var body: some View { @@ -24,8 +29,12 @@ struct TournamentBuildView: View { if tournament.groupStageCount > 0 { NavigationLink(value: Screen.groupStage) { LabeledContent { - Text(tournament.groupStageStatus()) - .multilineTextAlignment(.trailing) + if let groupStageStatus { + Text(groupStageStatus) + .multilineTextAlignment(.trailing) + } else { + ProgressView() + } } label: { Text("Poules") if tournament.shouldVerifyGroupStage { @@ -33,13 +42,20 @@ struct TournamentBuildView: View { } } } + .task { + groupStageStatus = await tournament.groupStageStatus() + } } if tournament.rounds().isEmpty == false { NavigationLink(value: Screen.round) { LabeledContent { - Text(tournament.bracketStatus()) - .multilineTextAlignment(.trailing) + if let bracketStatus { + Text(bracketStatus) + .multilineTextAlignment(.trailing) + } else { + ProgressView() + } } label: { Text("Tableau") if tournament.shouldVerifyBracket { @@ -47,42 +63,72 @@ struct TournamentBuildView: View { } } } + .task { + bracketStatus = await tournament.bracketStatus() + } } } Section { if tournament.state() != .finished { NavigationLink(value: Screen.schedule) { - let tournamentStatus = tournament.scheduleStatus() + let tournamentStatus = scheduleStatus LabeledContent { - Text(tournamentStatus.completion) + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() + } } label: { Text("Horaires") - Text(tournamentStatus.label) + if let tournamentStatus { + Text(tournamentStatus.label) + } } } + .task { + scheduleStatus = await tournament.scheduleStatus() + } NavigationLink(value: Screen.call) { - let tournamentStatus = tournament.callStatus() + let tournamentStatus = callStatus LabeledContent { - Text(tournamentStatus.completion) + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() + } } label: { Text("Convocations") - Text(tournamentStatus.label) + if let tournamentStatus { + Text(tournamentStatus.label) + } } } + .task { + callStatus = await tournament.callStatus() + } } if tournament.state() == .running || tournament.state() == .finished { NavigationLink(value: Screen.cashier) { - let tournamentStatus = tournament.cashierStatus() + let tournamentStatus = cashierStatus LabeledContent { - Text(tournamentStatus.completion) + if let tournamentStatus { + Text(tournamentStatus.completion) + } else { + ProgressView() + } } label: { Text("Encaissement") - Text(tournamentStatus.label) + if let tournamentStatus { + Text(tournamentStatus.label) + } } } + .task { + cashierStatus = await tournament.cashierStatus() + } } } }