From dc2371e95a900766c6e421b625e8d9264f2bfc8a Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 12 Jun 2024 16:11:10 +0200 Subject: [PATCH 1/3] add stuff --- PadelClub/Data/Tournament.swift | 20 +++++++++++++++++++ .../Views/Calling/SeedsCallingView.swift | 4 ++++ .../Views/GroupStage/GroupStageView.swift | 2 +- PadelClub/Views/Match/MatchSetupView.swift | 2 +- PadelClub/Views/Team/TeamPickerView.swift | 10 +++++++++- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 1c98977..e11d868 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1732,6 +1732,26 @@ class Tournament : ModelObject, Storable { } } + func replacementRangeExtended(groupStagePosition: Int) -> TeamRegistration.TeamRange? { + let selectedSortedTeams = selectedSortedTeams() + var left: TeamRegistration? = nil + if groupStagePosition == 0 { + left = seeds().last + } else { + let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition - 1 }).sorted(by: \.weight) + left = previousHat.last + } + var right: TeamRegistration? = nil + if groupStagePosition == teamsPerGroupStage - 1 { + right = nil + } else { + let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition + 1 }).sorted(by: \.weight) + right = previousHat.first + } + return (left: left, right: right) + } + + // MARK: - func insertOnServer() throws { diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index 3f4a33d..806bc30 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -26,6 +26,10 @@ struct SeedsCallingView: View { } } header: { Text(round.roundTitle()) + } footer: { + if let startDate = round.startDate ?? round.playedMatches().first?.startDate { + CallView(teams: seeds, callDate: startDate, matchFormat: round.matchFormat, roundLabel: round.roundTitle()) + } } } } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 67f2687..577a042 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -171,7 +171,7 @@ struct GroupStageView: View { VStack(alignment: .leading, spacing: 0) { Text("#\(index + 1)") .font(.caption) - TeamPickerView(teamPicked: { team in + TeamPickerView(groupStagePosition: index, teamPicked: { team in print(team.pasteData()) team.groupStage = groupStage.id team.groupStagePosition = index diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 4031257..08071b8 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -71,7 +71,7 @@ struct MatchSetupView: View { } HStack { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] - TeamPickerView(luckyLosers: luckyLosers, teamPicked: { team in + TeamPickerView(groupStagePosition: nil, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) if walkOutSpot { match.setLuckyLoser(team: team, teamPosition: teamPosition) diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index b4d6ab8..8b7b578 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -13,9 +13,10 @@ struct TeamPickerView: View { @Environment(\.dismiss) private var dismiss @State private var presentTeamPickerView: Bool = false @State private var searchField: String = "" + var groupStagePosition: Int? = nil var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) - + var body: some View { Button { presentTeamPickerView = true @@ -26,6 +27,13 @@ struct TeamPickerView: View { .sheet(isPresented: $presentTeamPickerView) { NavigationStack { List { + if let groupStagePosition, let replacementRangeExtended = tournament.replacementRangeExtended(groupStagePosition: groupStagePosition) { + Section { + GroupStageTeamReplacementView.TeamRangeView(teamRange: replacementRangeExtended, playerWeight: 0) + } header: { + Text("Même ligne en poule") + } + } let teams = tournament.selectedSortedTeams() if luckyLosers.isEmpty == false { Section { From 5ca9f8f462f914c7cd9a4d18124fc7b35c6a9e6a Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 12 Jun 2024 18:12:40 +0200 Subject: [PATCH 2/3] wip --- PadelClub/Data/MatchScheduler.swift | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index fb32e7a..c9be128 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -463,7 +463,7 @@ class MatchScheduler : ModelObject, Storable { let roundObject = match.roundObject! let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) print("courtsUnavailable \(courtsUnavailable)") - if courtIndex >= availableCourts - courtsUnavailable { + if courtPosition >= availableCourts - courtsUnavailable { return false } @@ -542,6 +542,11 @@ 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) + } dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) } } @@ -650,11 +655,24 @@ class MatchScheduler : ModelObject, Storable { courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability) }.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 - dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate) + let range = startDate.. Date: Wed, 12 Jun 2024 19:48:03 +0200 Subject: [PATCH 3/3] fix wip scheduler --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/MatchScheduler.swift | 45 ++++++++++--------- PadelClub/Views/Club/ClubRowView.swift | 1 - .../Views/Planning/PlanningSettingsView.swift | 25 ++++++++--- PadelClub/Views/Planning/PlanningView.swift | 2 +- 5 files changed, 45 insertions(+), 32 deletions(-) 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: ", "))