Indiquer le prochain match de chaque paire à la saisie du résultat (à chaque fin de match, le joueur me demande à quelle heure et sur quel court le prochain)

Dans gestionnaire/horaire, faire apparaître les matchs à venir plutôt que les matchs en cours / terminé
À la saisie du dernier match, informé du classement final immédiatement pour répondre à la paire
paca_championship
Raz 1 year ago
parent ac60d64acc
commit ff5da277db
  1. 6
      PadelClub/Data/Match.swift
  2. 16
      PadelClub/Data/Tournament.swift
  3. 4
      PadelClub/ViewModel/SetDescriptor.swift
  4. 2
      PadelClub/Views/Components/CopyPasteButtonView.swift
  5. 22
      PadelClub/Views/Match/MatchDetailView.swift
  6. 2
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  7. 120
      PadelClub/Views/Score/EditScoreView.swift
  8. 61
      PadelClub/Views/Score/FollowUpMatchView.swift
  9. 2
      PadelClub/Views/Score/SetInputView.swift
  10. 3
      PadelClub/Views/Tournament/TournamentRunningView.swift
  11. 7
      PadelClub/Views/ViewModifiers/ListRowViewModifier.swift

@ -186,6 +186,12 @@ defer {
return self.tournamentStore.teamRegistrations.findById(winningTeamId) return self.tournamentStore.teamRegistrations.findById(winningTeamId)
} }
func loser() -> TeamRegistration? {
guard let losingTeamId else { return nil }
return self.tournamentStore.teamRegistrations.findById(losingTeamId)
}
func localizedStartDate() -> String { func localizedStartDate() -> String {
if let startDate { if let startDate {
return startDate.formatted(date: .abbreviated, time: .shortened) return startDate.formatted(date: .abbreviated, time: .shortened)

@ -1088,6 +1088,8 @@ defer {
let duplicates : [PlayerRegistration] = duplicates(in: players) let duplicates : [PlayerRegistration] = duplicates(in: players)
let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil }) let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil })
let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players)
let homonyms = homonyms(in: players)
let ageInadequatePlayers = ageInadequatePlayers(in: players)
let isImported = players.anySatisfy({ $0.isImported() }) let isImported = players.anySatisfy({ $0.isImported() })
let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported) let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported)
let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
@ -1095,7 +1097,7 @@ defer {
let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil })
let waitingListInGroupStage = waitingList.filter({ $0.groupStage != 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 { func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool {
@ -1142,6 +1144,18 @@ defer {
return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) 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] { func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME #if _DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()

@ -30,4 +30,8 @@ struct SetDescriptor: Identifiable, Equatable {
return nil return nil
} }
} }
var shouldTieBreak: Bool {
setFormat.shouldTiebreak(scoreTeamOne: valueTeamOne ?? 0, scoreTeamTwo: valueTeamTwo ?? 0)
}
} }

@ -19,7 +19,7 @@ struct CopyPasteButtonView: View {
pasteboard.string = pasteValue pasteboard.string = pasteValue
copied = true copied = true
} label: { } 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)
} }
} }
} }

@ -35,6 +35,7 @@ struct MatchDetailView: View {
@State var showUserCreationView: Bool = false @State var showUserCreationView: Bool = false
@State private var presentFollowUpMatch: Bool = false @State private var presentFollowUpMatch: Bool = false
@State private var dismissWhenPresentFollowUpMatchIsDismissed: Bool = false @State private var dismissWhenPresentFollowUpMatchIsDismissed: Bool = false
@State private var presentRanking: Bool = false
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return match.tournamentStore return match.tournamentStore
@ -163,10 +164,31 @@ struct MatchDetailView: View {
FollowUpMatchView(match: match, dismissWhenPresentFollowUpMatchIsDismissed: $dismissWhenPresentFollowUpMatchIsDismissed) FollowUpMatchView(match: match, dismissWhenPresentFollowUpMatchIsDismissed: $dismissWhenPresentFollowUpMatchIsDismissed)
.tint(.master) .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: { .sheet(item: $scoreType, onDismiss: {
if match.hasEnded() { if match.hasEnded() {
if match.index == 0, match.roundObject?.parent == nil {
presentRanking = true
} else {
presentFollowUpMatch = true presentFollowUpMatch = true
} }
}
}) { scoreType in }) { scoreType in
let matchDescriptor = MatchDescriptor(match: match) let matchDescriptor = MatchDescriptor(match: match)
EditScoreView(matchDescriptor: matchDescriptor) EditScoreView(matchDescriptor: matchDescriptor)

@ -137,7 +137,7 @@ struct EditablePlayerView: View {
Button { Button {
player.validateLicenceId(licenseYearValidity) player.validateLicenceId(licenseYearValidity)
} label: { } label: {
Text("Valider la licence \(licenseYearValidity)") Text("Valider la licence \(String(licenseYearValidity))")
} }
} }
} }

@ -8,6 +8,37 @@
import SwiftUI import SwiftUI
import LeStorage 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 { struct EditScoreView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -15,20 +46,53 @@ struct EditScoreView: View {
@ObservedObject var matchDescriptor: MatchDescriptor @ObservedObject var matchDescriptor: MatchDescriptor
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let colorTeamOne: Color = .mint
let colorTeamTwo: Color = .cyan
func walkout(_ team: TeamPosition) { func walkout(_ team: TeamPosition) {
self.matchDescriptor.match?.setWalkOut(team) self.matchDescriptor.match?.setWalkOut(team)
save() save()
dismiss() 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 { var body: some View {
Form { Form {
Section { Section {
VStack(alignment: .leading) {
Text(matchDescriptor.teamLabelOne) 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 { HStack {
Spacer() Spacer()
VStack(alignment: .trailing) {
Text(matchDescriptor.teamLabelTwo).multilineTextAlignment(.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: { } footer: {
HStack { HStack {
Menu { Menu {
@ -67,6 +131,7 @@ struct EditScoreView: View {
matchDescriptor.setDescriptors = Array(matchDescriptor.setDescriptors[0...index]) matchDescriptor.setDescriptors = Array(matchDescriptor.setDescriptors[0...index])
} }
} }
.tint(getColor())
} }
if matchDescriptor.hasEnded { 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
}
} }

@ -12,6 +12,7 @@ struct FollowUpMatchView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let match: Match let match: Match
let readyMatches: [Match] let readyMatches: [Match]
let matchesLeft: [Match]
let isFree: Bool let isFree: Bool
@State private var sortingMode: SortingMode = .index @State private var sortingMode: SortingMode = .index
@ -21,6 +22,8 @@ struct FollowUpMatchView: View {
enum SortingMode: Int, Identifiable, CaseIterable { enum SortingMode: Int, Identifiable, CaseIterable {
var id: Int { self.rawValue } var id: Int { self.rawValue }
case winner
case loser
case index case index
case restingTime case restingTime
case court case court
@ -32,7 +35,11 @@ struct FollowUpMatchView: View {
case .court: case .court:
return "Terrain" return "Terrain"
case .restingTime: 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) _selectedCourt = .init(wrappedValue: match.courtIndex)
let currentTournament = match.currentTournament() let currentTournament = match.currentTournament()
let allMatches = currentTournament?.allMatches() ?? [] let allMatches = currentTournament?.allMatches() ?? []
self.matchesLeft = currentTournament?.matchesLeft(allMatches) ?? []
let runningMatches = currentTournament?.runningMatches(allMatches) ?? [] let runningMatches = currentTournament?.runningMatches(allMatches) ?? []
let readyMatches = currentTournament?.readyMatches(allMatches) ?? [] let readyMatches = currentTournament?.readyMatches(allMatches) ?? []
self.readyMatches = currentTournament?.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false) ?? [] self.readyMatches = currentTournament?.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false) ?? []
self.isFree = currentTournament?.isFree() ?? true 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] { var sortedMatches: [Match] {
switch sortingMode { switch sortingMode {
case .index: case .index:
@ -57,6 +96,18 @@ struct FollowUpMatchView: View {
return readyMatches.sorted(by: \.restingTimeForSorting) return readyMatches.sorted(by: \.restingTimeForSorting)
case .court: case .court:
return readyMatches.sorted(using: [.keyPath(\.courtIndexForSorting), .keyPath(\.restingTimeForSorting)], order: .ascending) 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 []
}
} }
} }
@ -95,9 +146,13 @@ struct FollowUpMatchView: View {
} }
Section { Section {
if sortedMatches.isEmpty == false {
ForEach(sortedMatches) { match in ForEach(sortedMatches) { match in
MatchRowView(match: match, matchViewStyle: .followUpStyle, updatedField: selectedCourt) MatchRowView(match: match, matchViewStyle: .followUpStyle, updatedField: selectedCourt)
} }
} else {
ContentUnavailableView("Aucun match à venir", systemImage: "xmark.circle", description: Text(contentUnavailableDescriptionLabel()))
}
} header: { } header: {
Picker(selection: $sortingMode) { Picker(selection: $sortingMode) {
ForEach(SortingMode.allCases) { sortingMode in ForEach(SortingMode.allCases) { sortingMode in
@ -112,7 +167,6 @@ struct FollowUpMatchView: View {
} }
.headerProminence(.increased) .headerProminence(.increased)
.textCase(nil) .textCase(nil)
} }
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Match à suivre") .navigationTitle("Match à suivre")
@ -120,6 +174,9 @@ struct FollowUpMatchView: View {
.toolbar { .toolbar {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
Button("Retour", role: .cancel) { Button("Retour", role: .cancel) {
if readyMatches.isEmpty && matchesLeft.isEmpty {
dismissWhenPresentFollowUpMatchIsDismissed = true
}
dismiss() dismiss()
} }
} }

@ -15,7 +15,7 @@ struct SetInputView: View {
var setFormat: SetFormat { setDescriptor.setFormat } var setFormat: SetFormat { setDescriptor.setFormat }
private var showTieBreakView: Bool { private var showTieBreakView: Bool {
setFormat.shouldTiebreak(scoreTeamOne: setDescriptor.valueTeamOne ?? 0, scoreTeamTwo: setDescriptor.valueTeamTwo ?? 0) setDescriptor.shouldTieBreak
} }
private var isMainViewTieBreakView: Bool { private var isMainViewTieBreakView: Bool {

@ -18,8 +18,9 @@ struct TournamentRunningView: View {
@ViewBuilder @ViewBuilder
var body: some View { 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: "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) // MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false)
let finishedMatches = tournament.finishedMatches(allMatches, limit: tournament.courtCount) 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()) MatchListView(section: "Dernier\(finishedMatches.count.pluralSuffix) match\(finishedMatches.count.pluralSuffix) terminé\(finishedMatches.count.pluralSuffix)", matches: finishedMatches, isExpanded: tournament.hasEnded())

@ -11,6 +11,7 @@ struct ListRowViewModifier: ViewModifier {
let isActive: Bool let isActive: Bool
let color: Color let color: Color
var hideColorVariation: Bool = false var hideColorVariation: Bool = false
let alignment: Alignment
func colorVariation() -> Color { func colorVariation() -> Color {
hideColorVariation ? Color(uiColor: .systemBackground) : color.variation() hideColorVariation ? Color(uiColor: .systemBackground) : color.variation()
@ -21,7 +22,7 @@ struct ListRowViewModifier: ViewModifier {
content content
.listRowBackground( .listRowBackground(
colorVariation() colorVariation()
.overlay(alignment: .leading, content: { .overlay(alignment: alignment, content: {
color.frame(width: 8) color.frame(width: 8)
}) })
) )
@ -32,7 +33,7 @@ struct ListRowViewModifier: ViewModifier {
} }
extension View { extension View {
func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false) -> some View { func listRowView(isActive: Bool = false, color: Color, hideColorVariation: Bool = false, alignment: Alignment = .leading) -> some View {
modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation)) modifier(ListRowViewModifier(isActive: isActive, color: color, hideColorVariation: hideColorVariation, alignment: alignment))
} }
} }

Loading…
Cancel
Save