overhault ongoing view

paca_championship
Raz 1 year ago
parent 969fa5094f
commit d6e87daa3d
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 16
      PadelClub/Data/DataStore.swift
  3. 95
      PadelClub/Data/Match.swift
  4. 2
      PadelClub/Data/MatchScheduler.swift
  5. 34
      PadelClub/Data/Tournament.swift
  6. 16
      PadelClub/Extensions/Date+Extensions.swift
  7. 4
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  8. 3
      PadelClub/Views/Components/MatchListView.swift
  9. 8
      PadelClub/Views/GroupStage/GroupStagesView.swift
  10. 22
      PadelClub/Views/Match/Components/MatchDateView.swift
  11. 30
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  12. 14
      PadelClub/Views/Match/MatchDetailView.swift
  13. 27
      PadelClub/Views/Match/MatchSummaryView.swift
  14. 2
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  15. 2
      PadelClub/Views/Navigation/MainView.swift
  16. 100
      PadelClub/Views/Navigation/Ongoing/OngoingContainerView.swift
  17. 121
      PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift
  18. 102
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  19. 21
      PadelClub/Views/Planning/PlanningByCourtView.swift
  20. 16
      PadelClub/Views/Planning/PlanningView.swift
  21. 197
      PadelClub/Views/Score/FollowUpMatchView.swift
  22. 8
      PadelClub/Views/Team/TeamRestingView.swift
  23. 4
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  24. 23
      PadelClub/Views/Tournament/TournamentRunningView.swift

@ -777,6 +777,12 @@
FFA252AD2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */; };
FFA252AE2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */; };
FFA252AF2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */; };
FFA252B12CDD2C080074E63F /* OngoingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */; };
FFA252B22CDD2C080074E63F /* OngoingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */; };
FFA252B32CDD2C080074E63F /* OngoingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */; };
FFA252B52CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; };
FFA252B62CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; };
FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; };
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; };
@ -1164,6 +1170,8 @@
FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubButtonView.swift; sourceTree = "<group>"; };
FFA252A82CDB70520074E63F /* PlayerStatisticView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStatisticView.swift; sourceTree = "<group>"; };
FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireStatisticView.swift; sourceTree = "<group>"; };
FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingContainerView.swift; sourceTree = "<group>"; };
FFA252B42CDD2C630074E63F /* OngoingDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingDestination.swift; sourceTree = "<group>"; };
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@ -1741,6 +1749,8 @@
isa = PBXGroup;
children = (
FF5D30552BD95B1100F2B93D /* OngoingView.swift */,
FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */,
FFA252B42CDD2C630074E63F /* OngoingDestination.swift */,
);
path = Ongoing;
sourceTree = "<group>";
@ -2433,6 +2443,7 @@
FF9267FC2BCE84870080F940 /* PlayerPayView.swift in Sources */,
FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */,
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */,
FFA252B22CDD2C080074E63F /* OngoingContainerView.swift in Sources */,
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */,
FF6761582CC7803600CC9BF2 /* DrawLogsView.swift in Sources */,
FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */,
@ -2482,6 +2493,7 @@
C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */,
C4C33F762C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */,
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */,
FFA252B62CDD2C6C0074E63F /* OngoingDestination.swift in Sources */,
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */,
FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */,
@ -2715,6 +2727,7 @@
FF4CBFD02C996C0600151637 /* PlayerPayView.swift in Sources */,
FF4CBFD12C996C0600151637 /* PlanningByCourtView.swift in Sources */,
FF4CBFD22C996C0600151637 /* FileImportManager.swift in Sources */,
FFA252B12CDD2C080074E63F /* OngoingContainerView.swift in Sources */,
FF4CBFD32C996C0600151637 /* TournamentButtonView.swift in Sources */,
FF6761592CC7803600CC9BF2 /* DrawLogsView.swift in Sources */,
FF4CBFD42C996C0600151637 /* FederalPlayer.swift in Sources */,
@ -2764,6 +2777,7 @@
FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */,
FF4CBFFD2C996C0600151637 /* User.swift in Sources */,
FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */,
FFA252B52CDD2C6C0074E63F /* OngoingDestination.swift in Sources */,
FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */,
FF4CC0002C996C0600151637 /* MockData.swift in Sources */,
FF4CC0012C996C0600151637 /* TeamDetailView.swift in Sources */,
@ -2976,6 +2990,7 @@
FF70FB4F2C90584900129CC2 /* PlayerPayView.swift in Sources */,
FF70FB502C90584900129CC2 /* PlanningByCourtView.swift in Sources */,
FF70FB512C90584900129CC2 /* FileImportManager.swift in Sources */,
FFA252B32CDD2C080074E63F /* OngoingContainerView.swift in Sources */,
FF70FB522C90584900129CC2 /* TournamentButtonView.swift in Sources */,
FF6761572CC7803600CC9BF2 /* DrawLogsView.swift in Sources */,
FF70FB532C90584900129CC2 /* FederalPlayer.swift in Sources */,
@ -3025,6 +3040,7 @@
FF70FB7C2C90584900129CC2 /* User.swift in Sources */,
C4C33F772C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */,
FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */,
FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */,
FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */,
FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */,
FF70FB802C90584900129CC2 /* TeamDetailView.swift in Sources */,

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

@ -151,7 +151,7 @@ defer {
case .wide, .title:
return "Match \(indexInRound(in: matches) + 1)"
case .short:
return "#\(indexInRound(in: matches) + 1)"
return "\(indexInRound(in: matches) + 1)"
}
}
@ -212,7 +212,7 @@ defer {
}
func cleanScheduleAndSave(_ targetStartDate: Date? = nil) {
startDate = targetStartDate
startDate = targetStartDate ?? startDate
confirmed = false
endDate = nil
followingMatch()?.cleanScheduleAndSave(nil)
@ -452,14 +452,14 @@ defer {
}
}
func roundTitle() -> String? {
func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String? {
if groupStage != nil { return groupStageObject?.groupStageTitle() }
else if let roundObject { return roundObject.roundTitle() }
else { return nil }
}
func roundAndMatchTitle() -> String {
[roundTitle(), matchTitle()].compactMap({ $0 }).joined(separator: " ")
func roundAndMatchTitle(_ displayStyle: DisplayStyle = .wide) -> String {
[roundTitle(displayStyle), matchTitle(displayStyle)].compactMap({ $0 }).joined(separator: " ")
}
func topPreviousRoundMatchIndex() -> Int {
@ -496,6 +496,15 @@ defer {
}
}
func loserMatch(_ teamPosition: TeamPosition) -> Match? {
if teamPosition == .one {
return roundObject?.upperBracketTopMatch(ofMatchIndex: index, previousRound: nil)
} else {
return roundObject?.upperBracketBottomMatch(ofMatchIndex: index, previousRound: nil)
}
}
var computedOrder: Int {
if let groupStageObject {
return (groupStageObject.index + 1) * 100 + groupStageObject.indexOf(index)
@ -681,6 +690,14 @@ defer {
}
}
func courtName(for selectedIndex: Int) -> String {
if let courtName = currentTournament()?.courtName(atIndex: selectedIndex) {
return courtName
} else {
return Court.courtIndexedTitle(atIndex: selectedIndex)
}
}
func courtCount() -> Int {
return currentTournament()?.courtCount ?? 1
}
@ -948,6 +965,74 @@ defer {
previousMatches().allSatisfy({ $0.isSeeded() == false })
}
func expectedToBeRunning() -> Bool {
guard let startDate else { return false }
return confirmed == false && startDate.timeIntervalSinceNow < 0
}
func expectedFormattedStartDate() -> String {
guard let startDate else { return "" }
return "était prévu à " + startDate.formattedAsHourMinute()
}
func runningDuration() -> String {
guard let startDate else { return "" }
return " depuis " + startDate.timeElapsedString()
}
func canBePlayedInSpecifiedCourt() -> Bool {
guard let courtIndex else { return false }
if expectedToBeRunning() {
return courtIsAvailable(courtIndex)
} else {
return true
}
}
typealias CourtIndexAndDate = (courtIndex: Int, startDate: Date)
func nextCourtsAvailable() -> [CourtIndexAndDate] {
guard let tournament = currentTournament() else { return [] }
let availableCourts = availableCourts()
let runningMatches = Tournament.runningMatches(tournament.allMatches())
let startDate = Date().withoutSeconds()
if runningMatches.isEmpty {
return availableCourts.map {
($0, startDate)
}
}
let optionalDates : [CourtIndexAndDate?] = runningMatches.map({ match in
guard let endDate = match.estimatedEndDate(tournament.additionalEstimationDuration) else { return nil }
guard let courtIndex = match.courtIndex else { return nil }
if endDate <= startDate {
return (courtIndex, startDate.addingTimeInterval(600))
} else {
return (courtIndex, endDate)
}
})
let dates : [CourtIndexAndDate] = optionalDates.compacted().sorted { a, b in
a.1 < b.1
}
return dates
}
func estimatedStartDate() -> CourtIndexAndDate? {
guard isReady() else { return nil }
guard let tournament = currentTournament() else { return nil }
let availableCourts = nextCourtsAvailable()
return availableCourts.first(where: { (courtIndex, startDate) in
let endDate = startDate.addingTimeInterval(TimeInterval(matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) * 60)
if tournament.courtUnavailable(courtIndex: courtIndex, from: startDate, to: endDate) == false {
return true
}
return false
})
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _round = "round"

@ -575,7 +575,7 @@ final class MatchScheduler : ModelObject, Storable {
print("Finished roundDispatcher with \(organizedSlots.count) scheduled matches")
return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound)
return MatchDispatcher(timedMatches: organizedSlots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound)
}
func dispatchCourts(courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) -> Date {

@ -514,7 +514,7 @@ final class Tournament : ModelObject, Storable {
}
func courtUsed() -> [Int] {
#if DEBUG //DEBUGING TIME
#if _DEBUGING_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -522,7 +522,7 @@ defer {
}
#endif
let runningMatches: [Match] = self.tournamentStore.matches.filter { $0.isRunning() }
let runningMatches: [Match] = DataStore.shared.runningMatches()
return Set(runningMatches.compactMap { $0.courtIndex }).sorted()
}
@ -1169,7 +1169,9 @@ defer {
// return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
}
func availableToStart(_ allMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] {
static let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.computedStartDateForSorting), .keyPath(\Match.index)]
static func availableToStart(_ allMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -1177,10 +1179,10 @@ defer {
print("func tournament availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return allMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(by: \.computedStartDateForSorting)
return allMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(using: defaultSorting, order: .ascending)
}
func runningMatches(_ allMatches: [Match]) -> [Match] {
static func runningMatches(_ allMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -1188,10 +1190,10 @@ defer {
print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting)
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(using: defaultSorting, order: .ascending)
}
func readyMatches(_ allMatches: [Match]) -> [Match] {
static func readyMatches(_ allMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -1199,10 +1201,10 @@ defer {
print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
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(using: defaultSorting, order: .ascending)
}
func matchesLeft(_ allMatches: [Match]) -> [Match] {
static func matchesLeft(_ allMatches: [Match]) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -1210,11 +1212,11 @@ defer {
print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return allMatches.filter({ $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting)
return allMatches.filter({ $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending)
}
func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] {
static func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -2398,6 +2400,16 @@ defer {
}
func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool {
guard let source = eventObject()?.courtsUnavailability else { return false }
let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex })
return courtLockedSchedule.anySatisfy({ dateInterval in
let range = startDate..<endDate
return dateInterval.range.overlaps(range)
})
}
// MARK: -
func insertOnServer() throws {

@ -36,6 +36,14 @@ enum TimeOfDay {
extension Date {
func withoutSeconds() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: calendar.component(.hour, from: self),
minute: calendar.component(.minute, from: self),
second: 0,
of: self)!
}
func localizedDate() -> String {
self.formatted(.dateTime.weekday().day().month()) + " à " + self.formattedAsHourMinute()
}
@ -232,6 +240,14 @@ extension Date {
self.formatted(.dateTime.weekday(.wide))
}
func timeElapsedString() -> String {
let timeInterval = abs(Date().timeIntervalSince(self))
let duration = Duration.seconds(timeInterval)
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow)
return formatStyle.format(duration)
}
static var hourMinuteFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute] // Customize units

@ -73,7 +73,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
)
.offset(x: 3, y: 3)
} else if let count, count > 0 {
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill")
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "ellipsis.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .logoRed)
.imageScale(.medium)
.background (
@ -93,7 +93,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
)
.offset(x: 3, y: 3)
} else if let count = destination.badgeValue(), count > 0 {
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill")
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "ellipsis.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .logoRed)
.imageScale(.medium)
.background (

@ -10,7 +10,6 @@ import SwiftUI
struct MatchListView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament
let section: String
let matches: [Match]?
@ -30,7 +29,6 @@ struct MatchListView: View {
@ViewBuilder
var body: some View {
if _shouldHide() == false {
Section {
DisclosureGroup(isExpanded: $isExpanded) {
if let matches {
ForEach(matches) { match in
@ -51,5 +49,4 @@ struct MatchListView: View {
}
}
}
}
}

@ -111,7 +111,7 @@ struct GroupStagesView: View {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination {
case .all:
let finishedMatches = tournament.finishedMatches(allMatches, limit: nil)
let finishedMatches = Tournament.finishedMatches(allMatches, limit: nil)
List {
if tournament.groupStageAdditionalQualified > 0 {
@ -148,10 +148,10 @@ struct GroupStagesView: View {
}
}
let runningMatches = tournament.runningMatches(allMatches)
let runningMatches = Tournament.runningMatches(allMatches)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "prêt à démarrer", matches: tournament.availableToStart(allMatches, in: runningMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "prêt à démarrer", matches: Tournament.availableToStart(allMatches, in: runningMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: Tournament.readyMatches(allMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false)
}
.navigationTitle("Toutes les poules")

@ -26,7 +26,17 @@ struct MatchDateView: View {
self.isReady = match.isReady()
self.hasWalkoutTeam = match.hasWalkoutTeam()
self.hasEnded = match.hasEnded()
if updatedField == nil, match.canBePlayedInSpecifiedCourt() {
self.updatedField = match.courtIndex
} else if let updatedField {
self.updatedField = updatedField
} else {
self.updatedField = match.availableCourts().first
}
}
var currentDate: Date {
Date().withoutSeconds()
}
var body: some View {
@ -41,7 +51,7 @@ struct MatchDateView: View {
if let updatedField {
match.setCourt(updatedField)
}
match.startDate = Date()
match.startDate = currentDate
match.endDate = nil
match.confirmed = true
_save()
@ -50,7 +60,7 @@ struct MatchDateView: View {
if let updatedField {
match.setCourt(updatedField)
}
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date())
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)
match.endDate = nil
match.confirmed = true
_save()
@ -59,7 +69,7 @@ struct MatchDateView: View {
if let updatedField {
match.setCourt(updatedField)
}
match.startDate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())
match.startDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)
match.endDate = nil
match.confirmed = true
_save()
@ -68,13 +78,15 @@ struct MatchDateView: View {
if let updatedField {
match.setCourt(updatedField)
}
match.startDate = Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: Date())
match.startDate = Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: currentDate)
match.endDate = nil
match.confirmed = true
_save()
}
} header: {
Text("Le match apparaîtra dans les en cours")
if let updatedField {
Text(match.courtName(for: updatedField))
}
}
} else {
Button("Décaler de \(estimatedDuration) minutes") {

@ -46,8 +46,26 @@ struct PlayerBlockView: View {
teamScore?.score?.components(separatedBy: ",") ?? []
}
private func _defaultLabel() -> String {
teamPosition.localizedLabel()
private func _defaultLabel() -> [String] {
var defaultLabels = [String]()
if let previous = match.previousMatch(teamPosition) {
defaultLabels.append("Gagnant \(previous.roundAndMatchTitle(.short))")
if previous.isReady() == true {
if let courtName = previous.courtName(), previous.isRunning() {
defaultLabels.append(courtName + "\(previous.runningDuration())")
}
}
} else if let loser = match.loserMatch(teamPosition) {
defaultLabels.append("Perdant \(loser.roundAndMatchTitle(.short))")
if loser.isReady() == true {
if let courtName = loser.courtName(), loser.isRunning() {
defaultLabels.append(courtName + "\(loser.runningDuration())")
}
}
} else {
defaultLabels.append(teamPosition.localizedLabel())
}
return defaultLabels
}
var body: some View {
@ -74,7 +92,13 @@ struct PlayerBlockView: View {
Text("longLabelPlayerTwo").lineLimit(1)
}
.opacity(0)
Text(_defaultLabel()).foregroundStyle(.secondary).lineLimit(1)
VStack(alignment: .leading) {
ForEach(_defaultLabel(), id: \.self) { name in
Text(name)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}
}

@ -162,7 +162,10 @@ struct MatchDetailView: View {
dismiss()
}
}) {
NavigationStack {
FollowUpMatchView(match: match, dismissWhenPresentFollowUpMatchIsDismissed: $dismissWhenPresentFollowUpMatchIsDismissed)
}
.tint(.master)
}
.sheet(isPresented: $presentRanking, content: {
@ -492,19 +495,20 @@ struct MatchDetailView: View {
Text("Horaire")
}
.onChange(of: startDateSetup) {
let date = Date().withoutSeconds()
switch startDateSetup {
case .customDate:
break
case .now:
startDate = Date()
startDate = date
case .nextRotation:
let baseDate = match.startDate ?? Date()
let baseDate = match.startDate ?? date
startDate = baseDate.addingTimeInterval(Double(rotationDuration) * 60)
case .previousRotation:
let baseDate = match.startDate ?? Date()
let baseDate = match.startDate ?? date
startDate = baseDate.addingTimeInterval(Double(-rotationDuration) * 60)
case .inMinutes(let minutes):
startDate = Date().addingTimeInterval(Double(minutes) * 60)
startDate = date.addingTimeInterval(Double(minutes) * 60)
}
}
}
@ -546,7 +550,7 @@ struct MatchDetailView: View {
}
RowButtonView("Valider") {
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
match.validateMatch(fromStartDate: startDateSetup == .now ? Date().withoutSeconds() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
save()

@ -59,13 +59,27 @@ struct MatchSummaryView: View {
}
}
Spacer()
if let courtName {
Spacer()
VStack(alignment: .trailing, spacing: 0) {
if let courtName, match.canBePlayedInSpecifiedCourt() {
if match.isRunning() == false {
Text("prévu")
}
Text(courtName)
.foregroundStyle(.gray)
.font(.caption)
} else if let first = match.availableCourts().first {
Text("possible")
Text(match.courtName(for: first))
} else {
if let estimatedStartDate = match.estimatedStartDate() {
Text(match.courtName(for: estimatedStartDate.0) + " possible")
Text("dans ~ " + estimatedStartDate.1.timeElapsedString())
} else {
Text("aucun terrain disponible")
}
}
}
.foregroundStyle(.secondary)
.font(.footnote)
}
.lineLimit(1)
}
@ -91,6 +105,11 @@ struct MatchSummaryView: View {
if matchViewStyle != .plainStyle {
HStack {
if match.expectedToBeRunning() {
Text(match.expectedFormattedStartDate())
.font(.footnote)
.foregroundStyle(.secondary)
}
Spacer()
MatchDateView(match: match, showPrefix: matchViewStyle == .tournamentResultStyle, updatedField: updatedField)
}

@ -124,7 +124,7 @@ struct CalendarView: View {
)
.overlay(alignment: .bottomTrailing) {
if let count = counts[day.dayInt] {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
Image(systemName: count <= 50 ? "\(count).circle.fill" : "ellipsis.circle.fill")
.foregroundColor(.secondary)
.imageScale(.medium)
.background (

@ -79,7 +79,7 @@ struct MainView: View {
TournamentOrganizerView()
.tabItem(for: .tournamentOrganizer)
.toolbarBackground(.visible, for: .tabBar)
OngoingView()
OngoingContainerView()
.tabItem(for: .ongoing)
.badge(self.dataStore.runningMatches().count)
.toolbarBackground(.visible, for: .tabBar)

@ -0,0 +1,100 @@
//
// OngoingContainerView.swift
// PadelClub
//
// Created by razmig on 07/11/2024.
//
import SwiftUI
import LeStorage
@Observable
class OngoingViewModel {
static let shared = OngoingViewModel()
var destination: OngoingDestination? = .running
var hideUnconfirmedMatches: Bool = false
var hideNotReadyMatches: Bool = false
func areFiltersEnabled() -> Bool {
hideUnconfirmedMatches || hideNotReadyMatches
}
let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.startDate!), .keyPath(\Match.index), .keyPath(\Match.courtIndexForSorting)]
var runningAndNextMatches: [Match] {
DataStore.shared.runningAndNextMatches().sorted(using: defaultSorting, order: .ascending)
}
var filteredRunningAndNextMatches: [Match] {
if destination == .followUp {
return runningAndNextMatches.filter({
(hideUnconfirmedMatches == false || hideUnconfirmedMatches == true && $0.confirmed)
&& (hideNotReadyMatches == false || hideNotReadyMatches == true && $0.isReady() )
})
} else {
return runningAndNextMatches
}
}
}
struct OngoingContainerView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@State private var showMatchPicker: Bool = false
var body: some View {
@Bindable var navigation = navigation
@Bindable var ongoingViewModel = OngoingViewModel.shared
NavigationStack(path: $navigation.ongoingPath) {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $ongoingViewModel.destination, destinations: OngoingDestination.allCases, nilDestinationIsValid: false)
switch ongoingViewModel.destination! {
case .running, .followUp:
OngoingView()
case .court, .free:
OngoingCourtView()
}
}
.toolbarBackground(.visible, for: .bottomBar)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Programmation")
.toolbar {
if ongoingViewModel.destination == .followUp {
ToolbarItem(placement: .topBarLeading) {
Menu {
Toggle(isOn: $ongoingViewModel.hideUnconfirmedMatches) {
Text("masquer non confirmés")
}
Toggle(isOn: $ongoingViewModel.hideNotReadyMatches) {
Text("masquer incomplets")
}
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.resizable()
.scaledToFit()
.frame(minHeight: 32)
}
.symbolVariant(ongoingViewModel.areFiltersEnabled() ? .fill : .none)
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
showMatchPicker = true
} label: {
Image(systemName: "rectangle.stack.badge.plus")
.resizable()
.scaledToFit()
.frame(minHeight: 32)
}
}
}
}
.environment(ongoingViewModel)
.sheet(isPresented: $showMatchPicker, content: {
FollowUpMatchView(selectedCourt: nil, allMatches: ongoingViewModel.runningAndNextMatches, autoDismiss: false)
.tint(.master)
})
}
}

@ -0,0 +1,121 @@
//
// OngoingDestination.swift
// PadelClub
//
// Created by razmig on 07/11/2024.
//
import SwiftUI
enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable {
var id: Int { self.rawValue }
static func == (lhs: OngoingDestination, rhs: OngoingDestination) -> Bool {
return lhs.id == rhs.id
}
case running
case followUp
case court
case free
var runningAndNextMatches: [Match] {
if self == .followUp {
OngoingViewModel.shared.filteredRunningAndNextMatches
} else {
OngoingViewModel.shared.runningAndNextMatches
}
}
var sortedMatches: [Match] {
return runningAndNextMatches.filter({ self.shouldDisplay($0) })
}
var filteredMatches: [Match] {
sortedMatches.filter({ OngoingDestination.running.shouldDisplay($0) })
}
var sortedCourtIndex: [Int?] {
let courtUsed = sortedMatches.grouped(by: { $0.courtIndex }).keys
let sortedNumbers = courtUsed.sorted { (a, b) -> Bool in
switch (a, b) {
case (nil, _): return false
case (_, nil): return true
case let (a?, b?): return a < b
}
}
return sortedNumbers
}
func contentUnavailable() -> some View {
switch self {
case .running:
ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi."))
case .followUp:
ContentUnavailableView("Aucun match à suivre", systemImage: "figure.tennis", description: Text("Tous vos matchs planifiés et confirmés, seront visibles ici, quelque soit le tournoi."))
case .court:
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:
ContentUnavailableView("Aucun terrain libre", systemImage: "sportscourt", description: Text("Les terrains libres seront visibles ici, quelque soit le tournoi."))
}
}
func localizedFilterModeLabel() -> String {
switch self {
case .running:
return "En cours"
case .followUp:
return "À suivre"
case .court:
return "Terrains"
case .free:
return "Libres"
}
}
func shouldDisplay(_ match: Match) -> Bool {
switch self {
case .running:
return match.isRunning()
case .court, .free:
return true
case .followUp:
return match.isRunning() == false
}
}
func selectionLabel(index: Int) -> String {
localizedFilterModeLabel()
}
func systemImage() -> String? {
switch self {
default:
return nil
}
}
func badgeValue() -> Int? {
switch self {
case .running:
sortedMatches.count
case .followUp:
sortedMatches.count
case .court:
sortedCourtIndex.filter({ index in
filteredMatches.filter({ $0.courtIndex == index }).isEmpty == false
}).count
case .free:
sortedCourtIndex.filter({ index in
filteredMatches.filter({ $0.courtIndex == index }).isEmpty
}).count
}
}
func badgeValueColor() -> Color? {
nil
}
func badgeImage() -> Badge? {
nil
}
}

@ -8,32 +8,33 @@
import SwiftUI
import LeStorage
extension Int: @retroactive Identifiable {
public var id: Int {
return self
}
}
struct OngoingView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore
@Environment(OngoingViewModel.self) private var ongoingViewModel: OngoingViewModel
@State private var sortByField: Bool = false
let fieldSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.courtIndexForSorting), .keyPath(\Match.startDate!)]
let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.startDate!), .keyPath(\Match.courtIndexForSorting)]
var matches: [Match] {
let sorting = self.sortByField ? fieldSorting : defaultSorting
return self.dataStore.runningMatches().sorted(using: sorting, order: .ascending)
var filterMode: OngoingDestination {
ongoingViewModel.destination!
}
var body: some View {
@Bindable var navigation = navigation
NavigationStack(path: $navigation.ongoingPath) {
let filteredMatches = filterMode.sortedMatches
List {
ForEach(matches) { match in
if let tournament = match.currentTournament() {
ForEach(filteredMatches) { match in
let tournament = match.currentTournament()
Section {
MatchRowView(match: match, matchViewStyle: .standardStyle)
} header: {
if let tournament {
HStack {
Text(tournament.tournamentTitle(.short))
Spacer()
@ -41,9 +42,12 @@ struct OngoingView: View {
Text("@" + club.clubTitle(.short))
}
}
}
} footer: {
HStack {
if let tournament {
Text(tournament.eventLabel())
}
#if DEBUG
Spacer()
FooterButtonView("copier l'id") {
@ -53,37 +57,61 @@ struct OngoingView: View {
#endif
}
}
}
}
}
.headerProminence(.increased)
.overlay {
if matches.isEmpty {
ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi."))
}
}
.navigationTitle("En cours")
.toolbarBackground(.visible, for: .bottomBar)
.toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .status) {
Picker(selection: $sortByField) {
Text("tri par date").tag(false)
Text("tri par terrain").tag(true)
} label: {
if filteredMatches.isEmpty {
filterMode.contentUnavailable()
}
}
}
}
struct OngoingCourtView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore
@Environment(OngoingViewModel.self) private var ongoingViewModel: OngoingViewModel
var filterMode: OngoingDestination {
ongoingViewModel.destination!
}
@State private var selectedCourtForFollowUp: Int?
var body: some View {
let sortedMatches = filterMode.sortedMatches
let filteredMatches = sortedMatches.filter({ OngoingDestination.running.shouldDisplay($0) })
List {
ForEach(filterMode.sortedCourtIndex, id: \.self) { index in
let courtFilteredMatches = filteredMatches.filter({ $0.courtIndex == index })
let title : String = (index == nil ? "Aucun terrain défini" : "Terrain #\(index! + 1)")
if (filterMode == .free && courtFilteredMatches.isEmpty) || (filterMode == .court && courtFilteredMatches.isEmpty == false) {
Section {
MatchListView(section: "En cours", matches: courtFilteredMatches, hideWhenEmpty: true, isExpanded: false)
if courtFilteredMatches.isEmpty {
Button("Choisir un match") {
selectedCourtForFollowUp = index
}
}
.pickerStyle(.segmented)
.fixedSize()
.offset(y: -3)
MatchListView(section: "À venir", matches: sortedMatches.filter({ $0.courtIndex == index && $0.hasStarted() == false }), isExpanded: false)
} header: {
Text(title)
}
}
}
}
.sheet(item: $selectedCourtForFollowUp, content: { selectedCourtForFollowUp in
FollowUpMatchView(selectedCourt: selectedCourtForFollowUp, allMatches: filterMode.runningAndNextMatches)
.tint(.master)
})
.headerProminence(.increased)
.overlay {
if (filteredMatches.isEmpty && filterMode != .free) || (filterMode == .free && filterMode.sortedCourtIndex.allSatisfy({ index in filteredMatches.filter({ $0.courtIndex == index }).isEmpty == false })) {
filterMode.contentUnavailable()
}
}
}
}
//#Preview {
// OngoingView()
//}

@ -19,7 +19,7 @@ struct PlanningByCourtView: View {
@State private var uuid: UUID = UUID()
var timeSlots: [Date:[Match]] {
Dictionary(grouping: matches) { $0.startDate ?? .distantFuture }
Dictionary(grouping: matches.filter({ $0.startDate != nil })) { $0.startDate! }
}
var days: [Date] {
@ -147,23 +147,4 @@ struct PlanningByCourtView: View {
}
}
}
private func _matchesCount(inDayInt dayInt: Int) -> Int {
timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count
}
private func _timeSlotView(key: Date, matches: [Match]) -> some View {
LabeledContent {
Text(self._formattedMatchCount(matches.count))
} label: {
Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold)
Text(Set(matches.compactMap { $0.roundTitle() }).joined(separator: ", "))
}
}
fileprivate func _formattedMatchCount(_ count: Int) -> String {
return "\(count.formatted()) match\(count.pluralSuffix)"
}
}

@ -79,8 +79,12 @@ struct PlanningView: View {
Picker(selection: $selectedDay) {
Text("Tous les jours").tag(nil as Date?)
ForEach(days, id: \.self) { day in
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
Text("Sans horaire").tag(day as Date?)
} else {
Text(day.formatted(.dateTime.day().weekday().month())).tag(day as Date?)
}
}
} label: {
Text("Jour")
}
@ -177,7 +181,11 @@ struct PlanningView: View {
}
} header: {
HStack {
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
Text("Sans horaire")
} else {
Text(day.formatted(.dateTime.day().weekday().month()))
}
Spacer()
let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots)
if showFinishedMatches {
@ -186,6 +194,10 @@ struct PlanningView: View {
Text(self._formattedMatchCount(count) + " restant\(count.pluralSuffix)")
}
}
} footer: {
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.")
}
}
.headerProminence(.increased)
}
@ -201,7 +213,11 @@ struct PlanningView: View {
LabeledContent {
Text(self._formattedMatchCount(matches.count))
} label: {
if key.monthYearFormatted == Date.distantFuture.monthYearFormatted {
Text("Aucun horaire")
} else {
Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold)
}
if matches.count <= tournament.courtCount {
let names = matches.sorted(by: \.computedOrder)
.compactMap({ $0.roundTitle() })

@ -10,17 +10,43 @@ import SwiftUI
struct FollowUpMatchView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(\.dismiss) private var dismiss
let match: Match
let match: Match?
let readyMatches: [Match]
let matchesLeft: [Match]
let isFree: Bool
var autoDismiss: Bool = true
@State private var sortingMode: SortingMode = .index
@State private var sortingMode: SortingMode? = .index
@State private var selectedCourt: Int?
@State private var checkCanPlay: Bool = false
@State private var seeAll: Bool = false
@Binding var dismissWhenPresentFollowUpMatchIsDismissed: Bool
enum SortingMode: Int, Identifiable, CaseIterable {
var matches: [Match] {
seeAll ? matchesLeft : readyMatches
}
enum SortingMode: Int, Identifiable, CaseIterable, Selectable, Equatable {
func selectionLabel(index: Int) -> String {
localizedSortingModeLabel()
}
func badgeValue() -> Int? {
nil
}
func badgeImage() -> Badge? {
nil
}
func badgeValueColor() -> Color? {
nil
}
static func == (lhs: SortingMode, rhs: SortingMode) -> Bool {
return lhs.id == rhs.id
}
var id: Int { self.rawValue }
case winner
case loser
@ -28,14 +54,23 @@ struct FollowUpMatchView: View {
case restingTime
case court
func canHaveSeeAllOption() -> Bool {
switch self {
case .index, .restingTime:
return true
case .winner, .loser, .court:
return false
}
}
func localizedSortingModeLabel() -> String {
switch self {
case .index:
return "Ordre"
return "Ordre prévu"
case .court:
return "Terrain"
case .restingTime:
return "Repos"
return "Temps de repos"
case .winner:
return "Gagnant"
case .loser:
@ -50,37 +85,50 @@ 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.matchesLeft = Tournament.matchesLeft(allMatches)
let runningMatches = Tournament.runningMatches(allMatches)
let readyMatches = Tournament.readyMatches(allMatches)
self.readyMatches = Tournament.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false)
self.isFree = currentTournament?.isFree() ?? true
}
init(selectedCourt: Int?, allMatches: [Match], autoDismiss: Bool = true) {
_dismissWhenPresentFollowUpMatchIsDismissed = .constant(false)
_selectedCourt = .init(wrappedValue: selectedCourt)
self.match = nil
self.autoDismiss = autoDismiss
self.matchesLeft = Tournament.matchesLeft(allMatches)
let runningMatches = Tournament.runningMatches(allMatches)
let readyMatches = Tournament.readyMatches(allMatches)
self.readyMatches = Tournament.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false)
self.isFree = false
}
var winningTeam: TeamRegistration? {
match.winner()
match?.winner()
}
var losingTeam: TeamRegistration? {
match.loser()
match?.loser()
}
var sortingModeCases: [SortingMode] {
var sortingModes = [SortingMode]()
if let winningTeam {
if winningTeam != nil {
sortingModes.append(.winner)
}
if let losingTeam {
if losingTeam != nil {
sortingModes.append(.loser)
}
sortingModes.append(.index)
sortingModes.append(.restingTime)
sortingModes.append(.court)
// sortingModes.append(.court)
return sortingModes
}
func contentUnavailableDescriptionLabel() -> String {
switch sortingMode {
switch sortingMode! {
case .winner:
if let winningTeam {
return "Aucun match à suivre pour \(winningTeam.teamLabel())"
@ -103,13 +151,13 @@ struct FollowUpMatchView: View {
}
var sortedMatches: [Match] {
switch sortingMode {
switch sortingMode! {
case .index:
return readyMatches
return matches
case .restingTime:
return readyMatches.sorted(by: \.restingTimeForSorting)
return matches.sorted(by: \.restingTimeForSorting)
case .court:
return readyMatches.sorted(using: [.keyPath(\.courtIndexForSorting), .keyPath(\.restingTimeForSorting)], order: .ascending)
return matchesLeft.filter({ $0.courtIndex == selectedCourt })
case .winner:
if let winningTeam, let followUpMatch = matchesLeft.first(where: { $0.containsTeamId(winningTeam.id) }) {
return [followUpMatch]
@ -127,63 +175,40 @@ struct FollowUpMatchView: View {
var body: some View {
NavigationStack {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $sortingMode, destinations: sortingModeCases, nilDestinationIsValid: false)
List {
Section {
Picker(selection: $selectedCourt) {
Text("Aucun").tag(nil as Int?)
if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)).tag(courtIndex as Int?)
}
} else {
ForEach(0..<20, id: \.self) { courtIndex in
Text(Court.courtIndexedTitle(atIndex: courtIndex)).tag(courtIndex as Int?)
}
}
} label: {
Text("Sur le terrain")
}
//
// Toggle(isOn: $checkCanPlay) {
// if isFree {
// Text("Vérifier le paiement ou la présence")
// } else {
// Text("Vérifier la présence")
// }
// }
// } footer: {
// if isFree {
// Text("Masque les matchs où un ou plusieurs joueurs qui ne sont pas encore arrivé")
// } else {
// Text("Masque les matchs où un ou plusieurs joueurs n'ont pas encore réglé ou qui ne sont pas encore arrivé")
// }
}
//
// Toggle(isOn: $checkCanPlay) {
// if isFree {
// Text("Vérifier le paiement ou la présence")
// } else {
// Text("Vérifier la présence")
// }
// }
// } footer: {
// if isFree {
// Text("Masque les matchs où un ou plusieurs joueurs qui ne sont pas encore arrivé")
// } else {
// Text("Masque les matchs où un ou plusieurs joueurs n'ont pas encore réglé ou qui ne sont pas encore arrivé")
// }
Section {
if sortedMatches.isEmpty == false {
ForEach(sortedMatches) { match in
Section {
MatchRowView(match: match, matchViewStyle: .followUpStyle, updatedField: selectedCourt)
}
}
} else {
ContentUnavailableView("Aucun match à venir", systemImage: "xmark.circle", description: Text(contentUnavailableDescriptionLabel()))
}
} header: {
Picker(selection: $sortingMode) {
ForEach(sortingModeCases) { sortingMode in
Text(sortingMode.localizedSortingModeLabel()).tag(sortingMode)
}
} label: {
Text("Méthode de tri")
}
.labelsHidden()
.pickerStyle(.segmented)
}
.headerProminence(.increased)
.textCase(nil)
}
.navigationTitle("À suivre sur")
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Match à suivre")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .bottomBar)
.toolbarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Retour", role: .cancel) {
@ -193,11 +218,55 @@ struct FollowUpMatchView: View {
dismiss()
}
}
ToolbarItem(placement: .status) {
Button {
seeAll.toggle()
} label: {
Text(seeAll ? "Masquer les matchs incomplets" : "Voir tous les matchs")
.underline()
}
.disabled(sortingMode?.canHaveSeeAllOption() == false)
}
// ToolbarItem(placement: .principal) {
// Picker(selection: $sortingMode) {
// ForEach(sortingModeCases) { sortingMode in
// Text(sortingMode.localizedSortingModeLabel()).tag(sortingMode)
// }
// } label: {
// Text("Méthode de tri")
// }
// .labelsHidden()
// .pickerStyle(.segmented)
// }
ToolbarItem(placement: .topBarTrailing) {
Picker(selection: $selectedCourt) {
Text("choix du terrain").tag(nil as Int?)
if let tournament = match?.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)).tag(courtIndex as Int?)
}
} else {
ForEach(0..<12, id: \.self) { courtIndex in
Text(Court.courtIndexedTitle(atIndex: courtIndex)).tag(courtIndex as Int?)
}
}
} label: {
Text("Sur le terrain")
}
.labelsHidden()
.underline()
}
}
}
.onChange(of: readyMatches) {
if autoDismiss {
dismissWhenPresentFollowUpMatchIsDismissed = true
dismiss()
}
}
}
}
}

@ -86,10 +86,10 @@ struct TeamRestingView: View {
.toolbarBackground(.visible, for: .navigationBar)
.onAppear {
let allMatches = tournament.allMatches()
let matchesLeft = tournament.matchesLeft(allMatches)
let runningMatches = tournament.runningMatches(allMatches)
let readyMatches = tournament.readyMatches(allMatches)
self.readyMatches = tournament.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false)
let matchesLeft = Tournament.matchesLeft(allMatches)
let runningMatches = Tournament.runningMatches(allMatches)
let readyMatches = Tournament.readyMatches(allMatches)
self.readyMatches = Tournament.availableToStart(readyMatches, in: runningMatches, checkCanPlay: false)
self.matchesLeft = matchesLeft
self.teams = tournament.selectedSortedTeams().filter({ $0.restingTime() != nil }).sorted(by: \.restingTimeForSorting)
}

@ -35,8 +35,8 @@ struct TournamentRankView: View {
if editMode?.wrappedValue.isEditing == false {
Section {
let all = tournament.allMatches()
let runningMatches = tournament.runningMatches(all)
let matchesLeft = tournament.readyMatches(all)
let runningMatches = Tournament.runningMatches(all)
let matchesLeft = Tournament.readyMatches(all)
MatchListView(section: "Matchs restant", matches: matchesLeft, hideWhenEmpty: false, isExpanded: false)
MatchListView(section: "Matchs en cours", matches: runningMatches, hideWhenEmpty: false, isExpanded: false)

@ -19,16 +19,27 @@ struct TournamentRunningView: View {
@ViewBuilder
var body: some View {
let runningMatches = tournament.runningMatches(allMatches)
let readyMatches = tournament.readyMatches(allMatches)
let runningMatches = Tournament.runningMatches(allMatches)
let matchesLeft = Tournament.matchesLeft(allMatches)
let readyMatches = Tournament.readyMatches(allMatches)
MatchListView(section: "à venir", matches: readyMatches, hideWhenEmpty: true, isExpanded: false)
Section {
MatchListView(section: "prêt à démarrer", matches: readyMatches, hideWhenEmpty: tournament.hasEnded(), isExpanded: true)
}
Section {
MatchListView(section: "à venir", matches: matchesLeft, hideWhenEmpty: true, isExpanded: false)
}
MatchListView(section: "en cours", matches: runningMatches, hideWhenEmpty: tournament.hasEnded())
// MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false)
let finishedMatches = tournament.finishedMatches(allMatches, limit: tournament.courtCount)
Section {
MatchListView(section: "en cours", matches: runningMatches, hideWhenEmpty: tournament.hasEnded(), isExpanded: false)
}
Section {
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())
}
}
}
//#Preview {

Loading…
Cancel
Save