diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index a44e108..f66a182 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1859,7 +1859,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1896,7 +1896,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index c9be128..9fb3349 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -363,6 +363,7 @@ class MatchScheduler : ModelObject, Storable { var rotationIndex = 0 var availableMatchs = flattenedMatches.filter({ $0.startDate == nil }) let courtsUnavailability = courtsUnavailability + var issueFound: Bool = false flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in if _startDate == nil { @@ -388,7 +389,7 @@ class MatchScheduler : ModelObject, Storable { var shouldStartAtDispatcherDate = rotationIndex > 0 - while availableMatchs.count > 0 { + while availableMatchs.count > 0 && issueFound == false { freeCourtPerRotation[rotationIndex] = [] let previousRotationSlots = slots.filter({ $0.rotationIndex == rotationIndex - 1 }) var rotationStartDate: Date = getNextStartDate(fromPreviousRotationSlots: previousRotationSlots, includeBreakTime: false) ?? dispatcherStartDate @@ -431,6 +432,13 @@ class MatchScheduler : ModelObject, Storable { rotationStartDate = rotationStartDate.addingTimeInterval(-difference) } } + } else if let first = availableMatchs.first { + let duration = first.matchFormat.getEstimatedDuration(additionalEstimationDuration) + let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability) + if courtsUnavailable == numberOfCourtsAvailablePerRotation { + print("issue") + issueFound = true + } } dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) @@ -450,7 +458,7 @@ class MatchScheduler : ModelObject, Storable { } - return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex) + return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound) } func dispatchCourts(availableCourts: Int, courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) { @@ -542,16 +550,18 @@ class MatchScheduler : ModelObject, Storable { }.sorted(by: \.1).map { $0.0 } } - let courtsUnavailable = courtsUnavailable(at: minimumTargetedEndDate, courtsUnavailability: courtsUnavailability) - - if let courtsUnavailable, courtsUnavailable == availableCourts { - minimumTargetedEndDate.addTimeInterval(3600) + + if let first = availableMatchs.first { + let duration = first.matchFormat.getEstimatedDuration(additionalEstimationDuration) + let courtsUnavailable = courtsUnavailable(startDate: minimumTargetedEndDate, duration: duration, courtsUnavailability: courtsUnavailability) + if courtsUnavailable < availableCourts { + dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) + } } - dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) } } - func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) { + @discardableResult func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) -> Bool { let upperRounds: [Round] = tournament.rounds() let allMatches: [Match] = tournament.allMatches() @@ -643,6 +653,8 @@ class MatchScheduler : ModelObject, Storable { } catch { Logger.error(error) } + + return roundDispatch.issueFound } @@ -656,18 +668,6 @@ class MatchScheduler : ModelObject, Storable { }.count } - func courtsUnavailable(at startDate: Date, courtsUnavailability: [DateInterval]?) -> Int? { - guard let courtsUnavailability else { return nil } - let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex }) - let courts = groupedBy.keys - return courts.filter { courtIndex in - let courtLockedSchedule = courtsUnavailability.filter({ $0.courtIndex == courtIndex }) - return courtLockedSchedule.anySatisfy({ dateInterval in - dateInterval.range.contains(startDate) - }) - }.count - } - func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date, source: [DateInterval]) -> Bool { let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex }) return courtLockedSchedule.anySatisfy({ dateInterval in @@ -676,9 +676,9 @@ class MatchScheduler : ModelObject, Storable { }) } - func updateSchedule(tournament: Tournament) { + func updateSchedule(tournament: Tournament) -> Bool { let lastDate = updateGroupStageSchedule(tournament: tournament) - updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) + return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) } } @@ -714,6 +714,7 @@ struct MatchDispatcher { let timedMatches: [TimeMatch] let freeCourtPerRotation: [Int: [Int]] let rotationCount: Int + let issueFound: Bool } extension Match { diff --git a/PadelClub/Views/Club/ClubRowView.swift b/PadelClub/Views/Club/ClubRowView.swift index 85a64ed..7ba1656 100644 --- a/PadelClub/Views/Club/ClubRowView.swift +++ b/PadelClub/Views/Club/ClubRowView.swift @@ -19,7 +19,6 @@ struct ClubRowView: View { // .foregroundStyle(club.isFavorite() ? .green : .logoRed) // } } label: { - Text("Club") Text(club.name) } } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index f988534..a856901 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -17,6 +17,7 @@ struct PlanningSettingsView: View { @State private var isScheduling: Bool = false @State private var schedulingDone: Bool = false @State private var showOptions: Bool = false + @State private var issueFound: Bool = false init(tournament: Tournament) { self.tournament = tournament @@ -87,6 +88,11 @@ struct PlanningSettingsView: View { } } + if issueFound { + Text("Padel Club n'a pas réussi à définir un horaire pour tous les matchs de ce tournoi, à cause de la programmation d'autres épreuves ou de l'indisponibilité des terrains.") + .foregroundStyle(.logoRed) + } + NavigationLink { List { _optionsView() @@ -151,9 +157,10 @@ struct PlanningSettingsView: View { } RowButtonView("Horaire intelligent", role: .destructive) { await MainActor.run { + issueFound = false schedulingDone = false } - await _setupSchedule() + self.issueFound = await _setupSchedule() await MainActor.run { _save() schedulingDone = true @@ -173,9 +180,15 @@ struct PlanningSettingsView: View { } .overlay(alignment: .bottom) { if schedulingDone { - Label("Horaires mis à jour", systemImage: "checkmark.circle.fill") - .toastFormatted() - .deferredRendering(for: .seconds(2)) + if issueFound { + Label("Horaires mis à jour", systemImage: "xmark.circle.fill") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } else { + Label("Horaires mis à jour", systemImage: "checkmark.circle.fill") + .toastFormatted() + .deferredRendering(for: .seconds(2)) + } } } .onChange(of: tournament.startDate) { @@ -262,8 +275,8 @@ struct PlanningSettingsView: View { } } - private func _setupSchedule() async { - matchScheduler.updateSchedule(tournament: tournament) + private func _setupSchedule() async -> Bool { + return matchScheduler.updateSchedule(tournament: tournament) } private func _save() { diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 42b43dd..a4780a4 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -90,7 +90,7 @@ struct PlanningView: View { private func _timeSlotView(key: Date, matches: [Match]) -> some View { LabeledContent { - Text(self._formattedMatchCount(self.matches.count)) + 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: ", "))