Laurent 1 year ago
commit 367fbc6fea
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 37
      PadelClub/Data/MatchScheduler.swift
  3. 20
      PadelClub/Data/Tournament.swift
  4. 4
      PadelClub/Views/Calling/SeedsCallingView.swift
  5. 1
      PadelClub/Views/Club/ClubRowView.swift
  6. 2
      PadelClub/Views/GroupStage/GroupStageView.swift
  7. 2
      PadelClub/Views/Match/MatchSetupView.swift
  8. 25
      PadelClub/Views/Planning/PlanningSettingsView.swift
  9. 2
      PadelClub/Views/Planning/PlanningView.swift
  10. 10
      PadelClub/Views/Team/TeamPickerView.swift

@ -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;

@ -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]?) {
@ -463,7 +471,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,11 +550,18 @@ class MatchScheduler : ModelObject, Storable {
}.sorted(by: \.1).map { $0.0 }
}
dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability)
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)
}
}
}
}
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()
@ -638,6 +653,8 @@ class MatchScheduler : ModelObject, Storable {
} catch {
Logger.error(error)
}
return roundDispatch.issueFound
}
@ -650,17 +667,18 @@ class MatchScheduler : ModelObject, Storable {
courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability)
}.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..<endDate
return dateInterval.range.overlaps(range)
})
}
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)
}
}
@ -696,6 +714,7 @@ struct MatchDispatcher {
let timedMatches: [TimeMatch]
let freeCourtPerRotation: [Int: [Int]]
let rotationCount: Int
let issueFound: Bool
}
extension Match {

@ -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 {

@ -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())
}
}
}
}

@ -19,7 +19,6 @@ struct ClubRowView: View {
// .foregroundStyle(club.isFavorite() ? .green : .logoRed)
// }
} label: {
Text("Club")
Text(club.name)
}
}

@ -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

@ -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)

@ -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() {

@ -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: ", "))

@ -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 {

Loading…
Cancel
Save