From ff5da277dbba2cb7deaea50c7400cd12695962a9 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 11 Oct 2024 19:39:53 +0200 Subject: [PATCH] =?UTF-8?q?Indiquer=20le=20prochain=20match=20de=20chaque?= =?UTF-8?q?=20paire=20=C3=A0=20la=20saisie=20du=20r=C3=A9sultat=20(=C3=A0?= =?UTF-8?q?=20chaque=20fin=20de=20match,=20le=20joueur=20me=20demande=20?= =?UTF-8?q?=C3=A0=20quelle=20heure=20et=20sur=20quel=20court=20le=20procha?= =?UTF-8?q?in)=20Dans=20gestionnaire/horaire,=20faire=20appara=C3=AEtre=20?= =?UTF-8?q?les=20matchs=20=C3=A0=20venir=20plut=C3=B4t=20que=20les=20match?= =?UTF-8?q?s=20en=20cours=20/=20termin=C3=A9=20=C3=80=20la=20saisie=20du?= =?UTF-8?q?=20dernier=20match,=20inform=C3=A9=20du=20classement=20final=20?= =?UTF-8?q?imm=C3=A9diatement=20pour=20r=C3=A9pondre=20=C3=A0=20la=20paire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PadelClub/Data/Match.swift | 6 + PadelClub/Data/Tournament.swift | 16 ++- PadelClub/ViewModel/SetDescriptor.swift | 4 + .../Components/CopyPasteButtonView.swift | 2 +- PadelClub/Views/Match/MatchDetailView.swift | 24 +++- .../Components/EditablePlayerView.swift | 2 +- PadelClub/Views/Score/EditScoreView.swift | 124 +++++++++++++++++- PadelClub/Views/Score/FollowUpMatchView.swift | 67 +++++++++- PadelClub/Views/Score/SetInputView.swift | 2 +- .../Tournament/TournamentRunningView.swift | 3 +- .../ViewModifiers/ListRowViewModifier.swift | 7 +- 11 files changed, 241 insertions(+), 16 deletions(-) diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index acce813..0c25194 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -186,6 +186,12 @@ defer { return self.tournamentStore.teamRegistrations.findById(winningTeamId) } + func loser() -> TeamRegistration? { + guard let losingTeamId else { return nil } + return self.tournamentStore.teamRegistrations.findById(losingTeamId) + } + + func localizedStartDate() -> String { if let startDate { return startDate.formatted(date: .abbreviated, time: .shortened) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 0105ee0..e2e0a6f 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1088,6 +1088,8 @@ defer { let duplicates : [PlayerRegistration] = duplicates(in: players) let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil }) let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) + let homonyms = homonyms(in: players) + let ageInadequatePlayers = ageInadequatePlayers(in: players) let isImported = players.anySatisfy({ $0.isImported() }) let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported) let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) @@ -1095,7 +1097,7 @@ defer { let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil }) - return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + ageInadequatePlayers.count + homonyms.count } func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool { @@ -1141,6 +1143,18 @@ defer { #endif return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) } + + func matchesLeft(_ allMatches: [Match]) -> [Match] { + #if _DEBUG_TIME //DEBUGING TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + #endif + return allMatches.filter({ $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) + } + func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] { #if _DEBUG_TIME //DEBUGING TIME diff --git a/PadelClub/ViewModel/SetDescriptor.swift b/PadelClub/ViewModel/SetDescriptor.swift index 2811e4e..d5b8e5f 100644 --- a/PadelClub/ViewModel/SetDescriptor.swift +++ b/PadelClub/ViewModel/SetDescriptor.swift @@ -30,4 +30,8 @@ struct SetDescriptor: Identifiable, Equatable { return nil } } + + var shouldTieBreak: Bool { + setFormat.shouldTiebreak(scoreTeamOne: valueTeamOne ?? 0, scoreTeamTwo: valueTeamTwo ?? 0) + } } diff --git a/PadelClub/Views/Components/CopyPasteButtonView.swift b/PadelClub/Views/Components/CopyPasteButtonView.swift index 27c6ad2..0b5c976 100644 --- a/PadelClub/Views/Components/CopyPasteButtonView.swift +++ b/PadelClub/Views/Components/CopyPasteButtonView.swift @@ -19,7 +19,7 @@ struct CopyPasteButtonView: View { pasteboard.string = pasteValue copied = true } label: { - Label(copied ? "copié" : "copier", systemImage: "doc.on.doc").symbolVariant(copied ? .fill : .none) + Label(copied ? "Copié" : "Copier", systemImage: "doc.on.doc").symbolVariant(copied ? .fill : .none) } } } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index a4a63bb..b1c1fe3 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -35,6 +35,7 @@ struct MatchDetailView: View { @State var showUserCreationView: Bool = false @State private var presentFollowUpMatch: Bool = false @State private var dismissWhenPresentFollowUpMatchIsDismissed: Bool = false + @State private var presentRanking: Bool = false var tournamentStore: TournamentStore { return match.tournamentStore @@ -163,9 +164,30 @@ struct MatchDetailView: View { FollowUpMatchView(match: match, dismissWhenPresentFollowUpMatchIsDismissed: $dismissWhenPresentFollowUpMatchIsDismissed) .tint(.master) } + .sheet(isPresented: $presentRanking, content: { + if let currentTournament = match.currentTournament() { + NavigationStack { + TournamentRankView() + .environment(currentTournament) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Retour", role: .cancel) { + presentRanking = false + dismiss() + } + } + } + } + .tint(.master) + } + }) .sheet(item: $scoreType, onDismiss: { if match.hasEnded() { - presentFollowUpMatch = true + if match.index == 0, match.roundObject?.parent == nil { + presentRanking = true + } else { + presentFollowUpMatch = true + } } }) { scoreType in let matchDescriptor = MatchDescriptor(match: match) diff --git a/PadelClub/Views/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index b2955fb..cc4e393 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -137,7 +137,7 @@ struct EditablePlayerView: View { Button { player.validateLicenceId(licenseYearValidity) } label: { - Text("Valider la licence \(licenseYearValidity)") + Text("Valider la licence \(String(licenseYearValidity))") } } } diff --git a/PadelClub/Views/Score/EditScoreView.swift b/PadelClub/Views/Score/EditScoreView.swift index c1de469..06eb65c 100644 --- a/PadelClub/Views/Score/EditScoreView.swift +++ b/PadelClub/Views/Score/EditScoreView.swift @@ -8,12 +8,46 @@ import SwiftUI import LeStorage +struct ArrowView: View { + var direction: ArrowDirection // Enum for left or right direction + var isActive: Bool // Whether the arrow is visible + var color: Color + @State private var isAnimating = false + + enum ArrowDirection { + case left, right + } + + var body: some View { + Image(systemName: direction == .left ? "arrow.left" : "arrow.right") + .font(.title) + .foregroundColor(isActive ? color : .clear) // Arrow visible only when active + .opacity(isAnimating ? 1.0 : 0.4) // Animate opacity between 1.0 and 0.4 + .offset(x: isAnimating ? (direction == .left ? -5 : 5) : 0) // Slight left/right movement + .animation( + Animation.easeInOut(duration: 0.7).repeatForever(autoreverses: true), + value: isAnimating + ) + .onAppear { + isAnimating = true + } + .onDisappear { + isAnimating = false + } + .padding() + } +} + + struct EditScoreView: View { @EnvironmentObject var dataStore: DataStore @ObservedObject var matchDescriptor: MatchDescriptor @Environment(\.dismiss) private var dismiss + + let colorTeamOne: Color = .mint + let colorTeamTwo: Color = .cyan func walkout(_ team: TeamPosition) { self.matchDescriptor.match?.setWalkOut(team) @@ -21,14 +55,44 @@ struct EditScoreView: View { dismiss() } + func pointRange(winner: Bool) -> Int? { + guard let match = matchDescriptor.match else { return nil } + guard let tournament = match.currentTournament() else { + return nil + } + + let teamsCount = tournament.teamCount + guard let round = match.roundObject else { return nil } + + guard let seedInterval = round.seedInterval(), match.index == 0 else { + return nil + } + + return winner ? tournament.tournamentLevel.points(for: seedInterval.first - 1, count: teamsCount) : tournament.tournamentLevel.points(for: seedInterval.last - 1, count: teamsCount) + } + var body: some View { Form { Section { - Text(matchDescriptor.teamLabelOne) + VStack(alignment: .leading) { + Text(matchDescriptor.teamLabelOne) + if matchDescriptor.hasEnded, let pointRange = pointRange(winner: matchDescriptor.winner == .one) { + Text(pointRange.formatted(.number.sign(strategy: .always())) + " pts") + .bold() + } + } + .listRowView(isActive: true, color: colorTeamOne, hideColorVariation: true) HStack { Spacer() - Text(matchDescriptor.teamLabelTwo).multilineTextAlignment(.trailing) + VStack(alignment: .trailing) { + Text(matchDescriptor.teamLabelTwo).multilineTextAlignment(.trailing) + if matchDescriptor.hasEnded, let pointRange = pointRange(winner: matchDescriptor.winner == .two) { + Text(pointRange.formatted(.number.sign(strategy: .always())) + " pts") + .bold() + } + } } + .listRowView(isActive: true, color: colorTeamTwo, hideColorVariation: true, alignment: .trailing) } footer: { HStack { Menu { @@ -67,6 +131,7 @@ struct EditScoreView: View { matchDescriptor.setDescriptors = Array(matchDescriptor.setDescriptors[0...index]) } } + .tint(getColor()) } if matchDescriptor.hasEnded { @@ -112,4 +177,59 @@ struct EditScoreView: View { } } } + + + var teamOneSetupIsActive: Bool { + guard let setDescriptor = matchDescriptor.setDescriptors.last else { + return false + } + if setDescriptor.valueTeamOne == nil { + return true + } else if setDescriptor.valueTeamTwo == nil { + return false + } else if setDescriptor.tieBreakValueTeamOne == nil, setDescriptor.shouldTieBreak { + return true + } else if setDescriptor.tieBreakValueTeamTwo == nil, setDescriptor.shouldTieBreak { + return false + } + + return false + } + + var teamTwoSetupIsActive: Bool { + guard let setDescriptor = matchDescriptor.setDescriptors.last else { + return false + } + + if setDescriptor.valueTeamOne == nil { + return false + } else if setDescriptor.valueTeamTwo == nil { + return true + } else if setDescriptor.tieBreakValueTeamOne == nil, setDescriptor.shouldTieBreak { + return false + } else if setDescriptor.tieBreakValueTeamTwo == nil, setDescriptor.shouldTieBreak { + return true + } + + return true + } + + func getColor() -> Color { + guard let setDescriptor = matchDescriptor.setDescriptors.last else { + return .master + } + + if setDescriptor.valueTeamOne == nil { + return colorTeamOne + } else if setDescriptor.valueTeamTwo == nil { + return colorTeamTwo + } else if setDescriptor.tieBreakValueTeamOne == nil, setDescriptor.shouldTieBreak { + return colorTeamOne + } else if setDescriptor.tieBreakValueTeamTwo == nil, setDescriptor.shouldTieBreak { + return colorTeamTwo + } + + return colorTeamTwo + } + } diff --git a/PadelClub/Views/Score/FollowUpMatchView.swift b/PadelClub/Views/Score/FollowUpMatchView.swift index b65c800..994d723 100644 --- a/PadelClub/Views/Score/FollowUpMatchView.swift +++ b/PadelClub/Views/Score/FollowUpMatchView.swift @@ -12,6 +12,7 @@ struct FollowUpMatchView: View { @Environment(\.dismiss) private var dismiss let match: Match let readyMatches: [Match] + let matchesLeft: [Match] let isFree: Bool @State private var sortingMode: SortingMode = .index @@ -21,6 +22,8 @@ struct FollowUpMatchView: View { enum SortingMode: Int, Identifiable, CaseIterable { var id: Int { self.rawValue } + case winner + case loser case index case restingTime case court @@ -32,7 +35,11 @@ struct FollowUpMatchView: View { case .court: return "Terrain" case .restingTime: - return "Temps de repos" + return "Repos" + case .winner: + return "Gagnant" + case .loser: + return "Perdant" } } } @@ -43,12 +50,44 @@ struct FollowUpMatchView: View { _selectedCourt = .init(wrappedValue: match.courtIndex) let currentTournament = match.currentTournament() let allMatches = currentTournament?.allMatches() ?? [] + self.matchesLeft = currentTournament?.matchesLeft(allMatches) ?? [] let runningMatches = currentTournament?.runningMatches(allMatches) ?? [] let readyMatches = currentTournament?.readyMatches(allMatches) ?? [] self.readyMatches = currentTournament?.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false) ?? [] self.isFree = currentTournament?.isFree() ?? true } + var winningTeam: TeamRegistration? { + match.winner() + } + + var losingTeam: TeamRegistration? { + match.loser() + } + + func contentUnavailableDescriptionLabel() -> String { + switch sortingMode { + case .winner: + if let winningTeam { + return "Aucun match à suivre pour \(winningTeam.teamLabel())" + } else { + return "La paire gagnante n'a pas été décidé" + } + case .loser: + if let losingTeam { + return "Aucun match à suivre pour \(losingTeam.teamLabel())" + } else { + return "La paire perdante n'a pas été décidé" + } + case .index: + return "Ce tournoi n'a aucun match prêt à démarrer" + case .restingTime: + return "Ce tournoi n'a aucun match prêt à démarrer" + case .court: + return "Ce tournoi n'a aucun match prêt à démarrer" + } + } + var sortedMatches: [Match] { switch sortingMode { case .index: @@ -57,6 +96,18 @@ struct FollowUpMatchView: View { return readyMatches.sorted(by: \.restingTimeForSorting) case .court: return readyMatches.sorted(using: [.keyPath(\.courtIndexForSorting), .keyPath(\.restingTimeForSorting)], order: .ascending) + case .winner: + if let winningTeam, let followUpMatch = matchesLeft.first(where: { $0.containsTeamId(winningTeam.id) }) { + return [followUpMatch] + } else { + return [] + } + case .loser: + if let losingTeam, let followUpMatch = matchesLeft.first(where: { $0.containsTeamId(losingTeam.id) }) { + return [followUpMatch] + } else { + return [] + } } } @@ -93,10 +144,14 @@ struct FollowUpMatchView: View { // Text("Masque les matchs où un ou plusieurs joueurs n'ont pas encore réglé ou qui ne sont pas encore arrivé") // } } - + Section { - ForEach(sortedMatches) { match in - MatchRowView(match: match, matchViewStyle: .followUpStyle, updatedField: selectedCourt) + if sortedMatches.isEmpty == false { + ForEach(sortedMatches) { match in + MatchRowView(match: match, matchViewStyle: .followUpStyle, updatedField: selectedCourt) + } + } else { + ContentUnavailableView("Aucun match à venir", systemImage: "xmark.circle", description: Text(contentUnavailableDescriptionLabel())) } } header: { Picker(selection: $sortingMode) { @@ -112,7 +167,6 @@ struct FollowUpMatchView: View { } .headerProminence(.increased) .textCase(nil) - } .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Match à suivre") @@ -120,6 +174,9 @@ struct FollowUpMatchView: View { .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Retour", role: .cancel) { + if readyMatches.isEmpty && matchesLeft.isEmpty { + dismissWhenPresentFollowUpMatchIsDismissed = true + } dismiss() } } diff --git a/PadelClub/Views/Score/SetInputView.swift b/PadelClub/Views/Score/SetInputView.swift index 3e4c787..504710e 100644 --- a/PadelClub/Views/Score/SetInputView.swift +++ b/PadelClub/Views/Score/SetInputView.swift @@ -15,7 +15,7 @@ struct SetInputView: View { var setFormat: SetFormat { setDescriptor.setFormat } private var showTieBreakView: Bool { - setFormat.shouldTiebreak(scoreTeamOne: setDescriptor.valueTeamOne ?? 0, scoreTeamTwo: setDescriptor.valueTeamTwo ?? 0) + setDescriptor.shouldTieBreak } private var isMainViewTieBreakView: Bool { diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 626a82e..6c61e24 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -18,8 +18,9 @@ struct TournamentRunningView: View { @ViewBuilder var body: some View { + MatchListView(section: "à venir", matches: tournament.readyMatches(allMatches), hideWhenEmpty: true, isExpanded: false) + MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches), hideWhenEmpty: tournament.hasEnded()) -// MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), isExpanded: false) // MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false) let finishedMatches = tournament.finishedMatches(allMatches, limit: tournament.courtCount) MatchListView(section: "Dernier\(finishedMatches.count.pluralSuffix) match\(finishedMatches.count.pluralSuffix) terminé\(finishedMatches.count.pluralSuffix)", matches: finishedMatches, isExpanded: tournament.hasEnded()) diff --git a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift index bcd0f36..6cfc20a 100644 --- a/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift +++ b/PadelClub/Views/ViewModifiers/ListRowViewModifier.swift @@ -11,6 +11,7 @@ struct ListRowViewModifier: ViewModifier { let isActive: Bool let color: Color var hideColorVariation: Bool = false + let alignment: Alignment func colorVariation() -> Color { hideColorVariation ? Color(uiColor: .systemBackground) : color.variation() @@ -21,7 +22,7 @@ struct ListRowViewModifier: ViewModifier { content .listRowBackground( colorVariation() - .overlay(alignment: .leading, content: { + .overlay(alignment: alignment, content: { color.frame(width: 8) }) ) @@ -32,7 +33,7 @@ struct ListRowViewModifier: ViewModifier { } extension View { - func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false) -> some View { - modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation)) + func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false, alignment: Alignment = .leading) -> some View { + modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation, alignment: alignment)) } }