add rotation knowledge

paca_championship
Raz 1 year ago
parent 1fb4b7294e
commit f60daee81f
  1. 13
      PadelClub/Data/DataStore.swift
  2. 47
      PadelClub/Data/Match.swift
  3. 6
      PadelClub/Extensions/FixedWidthInteger+Extensions.swift
  4. 36
      PadelClub/Views/Match/MatchSummaryView.swift
  5. 2
      PadelClub/Views/Navigation/Ongoing/OngoingContainerView.swift
  6. 22
      PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift
  7. 4
      PadelClub/Views/Score/FollowUpMatchView.swift

@ -324,4 +324,17 @@ class DataStore: ObservableObject {
return runningMatches return runningMatches
} }
func endMatches() -> [Match] {
let dateNow : Date = Date()
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
for tournament in lastTournaments {
let matches = tournament.tournamentStore.matches.filter { match in
match.hasEnded() }
runningMatches.append(contentsOf: matches)
}
return runningMatches.sorted(by: \.endDate!, order: .descending)
}
} }

@ -496,6 +496,11 @@ defer {
} }
} }
func loserMatches() -> [Match] {
guard let roundObject else { return [] }
return [roundObject.upperBracketTopMatch(ofMatchIndex: index, previousRound: nil), roundObject.upperBracketBottomMatch(ofMatchIndex: index, previousRound: nil)].compactMap({ $0 })
}
func loserMatch(_ teamPosition: TeamPosition) -> Match? { func loserMatch(_ teamPosition: TeamPosition) -> Match? {
if teamPosition == .one { if teamPosition == .one {
return roundObject?.upperBracketTopMatch(ofMatchIndex: index, previousRound: nil) return roundObject?.upperBracketTopMatch(ofMatchIndex: index, previousRound: nil)
@ -718,7 +723,7 @@ defer {
func availableCourts() -> [Int] { func availableCourts() -> [Int] {
let courtUsed = currentTournament()?.courtUsed() ?? [] let courtUsed = currentTournament()?.courtUsed() ?? []
return Array(Set(allCourts().map { $0 }).subtracting(Set(courtUsed))) return Set(allCourts().map { $0 }).subtracting(Set(courtUsed)).sorted()
} }
func removeCourt() { func removeCourt() {
@ -970,9 +975,27 @@ defer {
return confirmed == false && startDate.timeIntervalSinceNow < 0 return confirmed == false && startDate.timeIntervalSinceNow < 0
} }
func expectedFormattedStartDate() -> String { func expectedFormattedStartDate(updatedField: Int?) -> String {
guard let startDate else { return "" } guard let startDate else { return "" }
return "était prévu à " + startDate.formattedAsHourMinute() guard hasEnded() == false, isRunning() == false else { return "" }
let depthReadiness = depthReadiness()
if depthReadiness == 0 {
let availableCourts = availableCourts()
if canBePlayedInSpecifiedCourt() {
return "possible tout de suite"
} else if let updatedField, availableCourts.contains(updatedField) {
return "possible tout de suite \(courtName(for: updatedField))"
} else if let first = availableCourts.first {
return "possible tout de suite \(courtName(for: first))"
} else if let estimatedStartDate = estimatedStartDate() {
return "dans ~" + estimatedStartDate.1.timeElapsedString() + " " + courtName(for: estimatedStartDate.0)
}
return "était prévu à " + startDate.formattedAsHourMinute()
} else if depthReadiness == 1 {
return "possible prochaine rotation"
} else {
return "dans \(depthReadiness) rotation\(depthReadiness.pluralSuffix), ~\((getDuration() * depthReadiness).durationInHourMinutes())"
}
} }
func runningDuration() -> String { func runningDuration() -> String {
@ -1031,7 +1054,23 @@ defer {
return false return false
}) })
} }
func depthReadiness() -> Int {
// Base case: If this match is ready, the depth is 0
if isReady() {
return 0
}
// Recursive case: If not ready, check the maximum depth of readiness among previous matches
// If previousMatches() is empty, return a default depth of -1
let previousDepth = ancestors().map { $0.depthReadiness() }.max() ?? -1
return previousDepth + 1
}
func ancestors() -> [Match] {
previousMatches() + loserMatches()
}
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case _id = "id" case _id = "id"

@ -34,4 +34,10 @@ public extension FixedWidthInteger {
func formattedAsRawString() -> String { func formattedAsRawString() -> String {
String(self) String(self)
} }
func durationInHourMinutes() -> String {
let duration = Duration.seconds(self*60)
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow)
return formatStyle.format(duration)
}
} }

@ -63,23 +63,9 @@ struct MatchSummaryView: View {
} }
Spacer() Spacer()
VStack(alignment: .trailing, spacing: 0) { VStack(alignment: .trailing, spacing: 0) {
if match.hasEnded() == false, match.isRunning() == false { if let courtName {
if let courtName, match.canBePlayedInSpecifiedCourt() {
Text("prévu")
Text(courtName)
} else if let first = match.availableCourts().first {
Text("possible")
Text(match.courtName(for: first))
} else if let estimatedStartDate {
Text(match.courtName(for: estimatedStartDate.0) + " possible")
Text("dans ~ " + estimatedStartDate.1.timeElapsedString())
} else if let courtName {
Text(courtName)
} else {
Text("")
}
} else if let courtName {
Text(courtName) Text(courtName)
.strikethrough(match.courtIndex! != updatedField && match.isReady() && match.canBePlayedInSpecifiedCourt() == false)
} }
} }
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
@ -111,18 +97,32 @@ struct MatchSummaryView: View {
if matchViewStyle != .plainStyle { if matchViewStyle != .plainStyle {
HStack { HStack {
if match.expectedToBeRunning() { if match.expectedToBeRunning() {
Text(match.expectedFormattedStartDate()) Text(match.expectedFormattedStartDate(updatedField: updatedField))
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
Spacer() Spacer()
MatchDateView(match: match, showPrefix: matchViewStyle == .tournamentResultStyle, updatedField: updatedField ?? estimatedStartDate?.0) MatchDateView(match: match, showPrefix: matchViewStyle == .tournamentResultStyle, updatedField: possibleCourtIndex)
} }
} }
} }
.padding(.vertical, padding) .padding(.vertical, padding)
.monospacedDigit() .monospacedDigit()
} }
var possibleCourtIndex: Int? {
let availableCourts = match.availableCourts()
if match.canBePlayedInSpecifiedCourt() {
return nil
} else if let updatedField, availableCourts.contains(updatedField) {
return updatedField
} else if let first = availableCourts.first {
return first
} else if let estimatedStartDate {
return estimatedStartDate.0
}
return updatedField
}
} }
//#Preview { //#Preview {

@ -46,7 +46,7 @@ struct OngoingContainerView: View {
GenericDestinationPickerView(selectedDestination: $ongoingViewModel.destination, destinations: OngoingDestination.allCases, nilDestinationIsValid: false) GenericDestinationPickerView(selectedDestination: $ongoingViewModel.destination, destinations: OngoingDestination.allCases, nilDestinationIsValid: false)
switch ongoingViewModel.destination! { switch ongoingViewModel.destination! {
case .running, .followUp: case .running, .followUp, .over:
OngoingView() OngoingView()
case .court, .free: case .court, .free:
OngoingCourtView() OngoingCourtView()

@ -17,12 +17,16 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable
case followUp case followUp
case court case court
case free case free
case over
var runningAndNextMatches: [Match] { var runningAndNextMatches: [Match] {
if self == .followUp { switch self {
OngoingViewModel.shared.filteredRunningAndNextMatches case .running, .court, .free:
} else { return OngoingViewModel.shared.runningAndNextMatches
OngoingViewModel.shared.runningAndNextMatches case .followUp:
return OngoingViewModel.shared.filteredRunningAndNextMatches
case .over:
return DataStore.shared.endMatches()
} }
} }
@ -56,6 +60,8 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable
ContentUnavailableView("Aucun match en cours", systemImage: "sportscourt", description: Text("Tous vos terrains correspondant aux matchs en cours seront visibles ici, quelque soit le tournoi.")) ContentUnavailableView("Aucun match en cours", systemImage: "sportscourt", description: Text("Tous vos terrains correspondant aux matchs en cours seront visibles ici, quelque soit le tournoi."))
case .free: case .free:
ContentUnavailableView("Aucun terrain libre", systemImage: "sportscourt", description: Text("Les terrains libres seront visibles ici, quelque soit le tournoi.")) ContentUnavailableView("Aucun terrain libre", systemImage: "sportscourt", description: Text("Les terrains libres seront visibles ici, quelque soit le tournoi."))
case .over:
ContentUnavailableView("Aucun match terminé", systemImage: "clock.badge.xmark", description: Text("Les matchs terminés seront visibles ici, quelque soit le tournoi."))
} }
} }
@ -69,6 +75,8 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable
return "Terrains" return "Terrains"
case .free: case .free:
return "Libres" return "Libres"
case .over:
return "Finis"
} }
} }
@ -80,6 +88,8 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable
return true return true
case .followUp: case .followUp:
return match.isRunning() == false return match.isRunning() == false
case .over:
return match.hasEnded()
} }
} }
@ -96,9 +106,7 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable
func badgeValue() -> Int? { func badgeValue() -> Int? {
switch self { switch self {
case .running: case .running, .followUp, .over:
sortedMatches.count
case .followUp:
sortedMatches.count sortedMatches.count
case .court: case .court:
sortedCourtIndex.filter({ index in sortedCourtIndex.filter({ index in

@ -14,12 +14,12 @@ struct FollowUpMatchView: View {
let readyMatches: [Match] let readyMatches: [Match]
let matchesLeft: [Match] let matchesLeft: [Match]
let isFree: Bool let isFree: Bool
var autoDismiss: Bool = true var autoDismiss: Bool = false
@State private var sortingMode: SortingMode? = .index @State private var sortingMode: SortingMode? = .index
@State private var selectedCourt: Int? @State private var selectedCourt: Int?
@State private var checkCanPlay: Bool = false @State private var checkCanPlay: Bool = false
@State private var seeAll: Bool = false @State private var seeAll: Bool = true
@Binding var dismissWhenPresentFollowUpMatchIsDismissed: Bool @Binding var dismissWhenPresentFollowUpMatchIsDismissed: Bool
var matches: [Match] { var matches: [Match] {

Loading…
Cancel
Save