fix stuff around stage

multistore
Razmig Sarkissian 2 years ago
parent 69dcc5a16d
commit bcd71ffa06
  1. 16
      PadelClub/Data/Round.swift
  2. 17
      PadelClub/Extensions/Date+Extensions.swift
  3. 50
      PadelClub/ViewModel/MatchScheduler.swift
  4. 2
      PadelClub/Views/Calling/CallView.swift
  5. 6
      PadelClub/Views/Components/RowButtonView.swift
  6. 2
      PadelClub/Views/GroupStage/GroupStageView.swift
  7. 6
      PadelClub/Views/Match/MatchSummaryView.swift
  8. 130
      PadelClub/Views/Planning/Components/DateUpdateManagerView.swift
  9. 21
      PadelClub/Views/Planning/GroupStageScheduleEditorView.swift
  10. 57
      PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift
  11. 50
      PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift
  12. 38
      PadelClub/Views/Planning/MatchScheduleEditorView.swift
  13. 36
      PadelClub/Views/Planning/PlanningSettingsView.swift
  14. 75
      PadelClub/Views/Planning/PlanningView.swift
  15. 39
      PadelClub/Views/Planning/RoundScheduleEditorView.swift
  16. 75
      PadelClub/Views/Planning/SchedulerView.swift
  17. 6
      PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift

@ -285,7 +285,19 @@ class Round: ModelObject, Storable {
}
}
func estimatedEndDate(_ additionalEstimationDuration: Int) -> Date? {
enabledMatches().last?.estimatedEndDate(additionalEstimationDuration)
}
func getLoserRoundStartDate() -> Date? {
loserRoundsAndChildren().first(where: { $0.isDisabled() == false })?.enabledMatches().first?.startDate
}
func estimatedLoserRoundEndDate(_ additionalEstimationDuration: Int) -> Date? {
let lastMatch = loserRoundsAndChildren().last(where: { $0.isDisabled() == false })?.enabledMatches().last
return lastMatch?.estimatedEndDate(additionalEstimationDuration)
}
func disabledMatches() -> [Match] {
_matches().filter({ $0.disabled })
}
@ -400,12 +412,12 @@ class Round: ModelObject, Storable {
return Store.main.findById(parent)
}
func updateIfRequiredMatchFormat(_ updatedMatchFormat: MatchFormat, andLoserBracket: Bool) {
func updateMatchFormat(_ updatedMatchFormat: MatchFormat, checkIfPossible: Bool, andLoserBracket: Bool) {
if updatedMatchFormat.weight < self.matchFormat.weight {
updateMatchFormatAndAllMatches(updatedMatchFormat)
if andLoserBracket {
loserRoundsAndChildren().forEach { round in
round.updateIfRequiredMatchFormat(updatedMatchFormat, andLoserBracket: true)
round.updateMatchFormat(updatedMatchFormat, checkIfPossible: checkIfPossible, andLoserBracket: true)
}
}
}

@ -37,7 +37,15 @@ enum TimeOfDay {
extension Date {
func localizedDate() -> String {
self.formatted(.dateTime.weekday().day().month()) + " à " + self.formatted(.dateTime.hour().minute())
self.formatted(.dateTime.weekday().day().month()) + " à " + self.formattedAsHourMinute()
}
func formattedAsHourMinute() -> String {
formatted(.dateTime.hour().minute())
}
func formattedAsDate() -> String {
formatted(.dateTime.weekday().day(.twoDigits).month().year())
}
var monthYearFormatted: String {
@ -193,6 +201,11 @@ extension Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: 23, minute: 59, second: 59, of: self)!
}
func atNine() -> Date {
let calendar = Calendar.current
return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: self)!
}
}
extension Date {
@ -203,7 +216,7 @@ extension Date {
extension Date {
func localizedTime() -> String {
self.formatted(.dateTime.hour().minute())
self.formattedAsHourMinute()
}
func localizedDay() -> String {

@ -94,6 +94,43 @@ class MatchScheduler {
options.contains(.rotationDifferenceIsImportant)
}
@discardableResult
func updateGroupStageSchedule(tournament: Tournament) -> Date {
let groupStageCourtCount = tournament.groupStageCourtCount ?? 1
let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
courtsUnavailability = tournament.eventObject?.courtsUnavailability
let matches = groupStages.flatMap({ $0._matches() })
matches.forEach({
$0.removeCourt()
$0.startDate = nil
})
var lastDate : Date = tournament.startDate
groupStages.chunked(into: groupStageCourtCount).forEach { groups in
groups.forEach({ $0.startDate = lastDate })
try? DataStore.shared.groupStages.addOrUpdate(contentOfs: groups)
let dispatch = groupStageDispatcher(numberOfCourtsAvailablePerRotation: numberOfCourtsAvailablePerRotation, groupStages: groups, startingDate: lastDate)
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60)
}
match.setCourt(matchSchedule.courtIndex)
}
}
}
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
return lastDate
}
func groupStageDispatcher(numberOfCourtsAvailablePerRotation: Int, groupStages: [GroupStage], startingDate: Date?) -> GroupStageMatchDispatcher {
let _groupStages = groupStages
@ -464,8 +501,10 @@ class MatchScheduler {
}
}
func updateSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) {
func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) {
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
courtsUnavailability = tournament.eventObject?.courtsUnavailability
let upperRounds = tournament.rounds()
let allMatches = tournament.allMatches()
@ -567,4 +606,11 @@ class MatchScheduler {
dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate)
})
}
func updateSchedule(tournament: Tournament) {
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
courtsUnavailability = tournament.eventObject?.courtsUnavailability
let lastDate = updateGroupStageSchedule(tournament: tournament)
updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate)
}
}

@ -18,7 +18,7 @@ struct CallView: View {
VStack(spacing: 0) {
HStack {
if let startDate {
Text(startDate.formatted(.dateTime.hour().minute()))
Text(startDate.formattedAsHourMinute())
} else {
Text("Aucun horaire")
}

@ -77,7 +77,11 @@ struct RowButtonView: View {
}
.overlay {
if isLoading {
ProgressView()
ZStack {
Color.master
ProgressView()
.tint(.white)
}
}
}
.disabled(isLoading)

@ -41,7 +41,7 @@ struct GroupStageView: View {
_groupStageView()
} header: {
if let startDate = groupStage.startDate {
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute()))
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formattedAsHourMinute())
}
} footer: {
HStack {

@ -174,7 +174,7 @@ struct MatchSummaryView: View {
// }
// } else {
// if let endDate = match.endDate {
// Text(endDate.formatted(.dateTime.hour().minute()))
// Text(endDate.formattedAsHourMinute())
// }
// }
// }
@ -203,9 +203,9 @@ struct MatchSummaryView: View {
// secondsComponent: Int64(endDate.timeIntervalSince(startDate)),
// attosecondsComponent: 0
// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow))
// Text(startDate.formatted(.dateTime.hour().minute()))
// Text(startDate.formattedAsHourMinute())
// } else if startDate.timeIntervalSinceNow < 0 {
// Text(startDate.formatted(.dateTime.hour().minute()))
// Text(startDate.formattedAsHourMinute())
// } else {
// Text(startDate.formatted(.dateTime.day().month()))
// }

@ -7,61 +7,107 @@
import SwiftUI
enum DateUpdate {
case nextRotation
case previousRotation
case tomorrowAtNine
case inMinutes(Int)
case afterRound(Round)
case afterGroupStage(GroupStage)
}
struct DateUpdateManagerView: View {
struct DatePickingView: View {
let title: String
@Binding var startDate: Date
@Binding var currentDate: Date?
@State private var dateUpdated: Bool = false
var duration: Int?
var validateAction: () -> Void
var validateAction: (() async -> ())
@State private var confirmFollowingScheduleUpdate: Bool = false
@State private var updatingInProgress: Bool = false
var body: some View {
HStack {
Menu {
Button("à demain 9h") {
startDate = startDate.tomorrowAtNine
Section {
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
if confirmFollowingScheduleUpdate {
RowButtonView("Modifier la suite du programme") {
updatingInProgress = true
await validateAction()
updatingInProgress = false
confirmFollowingScheduleUpdate = false
}
if let duration {
Button("à la prochaine rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * 60)
}
} header: {
Text(title)
} footer: {
if confirmFollowingScheduleUpdate && updatingInProgress == false {
FooterButtonView("non, ne pas modifier la suite") {
currentDate = startDate
confirmFollowingScheduleUpdate = false
}
} else {
HStack {
Menu {
Button("à 9h") {
startDate = startDate.atNine()
}
Button("à demain 9h") {
startDate = startDate.tomorrowAtNine
}
if let duration {
Button("à la prochaine rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * 60)
}
Button("à la précédente rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * -60)
}
}
} label: {
Text("décaler")
.underline()
}
Button("à la précédente rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * -60)
.buttonStyle(.borderless)
Spacer()
if currentDate != nil {
FooterButtonView("retirer l'horaire bloqué") {
currentDate = nil
}
}
}
} label: {
Text("décaler")
.underline()
.buttonStyle(.borderless)
}
.buttonStyle(.borderless)
Spacer()
if dateUpdated {
FooterButtonView("valider l'horaire") {
validateAction()
dateUpdated = false
}
.onChange(of: startDate) {
confirmFollowingScheduleUpdate = true
}
.headerProminence(.increased)
}
}
struct MatchFormatPickingView: View {
@Binding var matchFormat: MatchFormat
var validateAction: (() async -> ())
@State private var confirmScheduleUpdate: Bool = false
@State private var updatingInProgress : Bool = false
var body: some View {
Section {
MatchFormatPickerView(headerLabel: "Format", matchFormat: $matchFormat)
if confirmScheduleUpdate {
RowButtonView("Recalculer les horaires") {
updatingInProgress = true
await validateAction()
updatingInProgress = false
confirmScheduleUpdate = false
}
} else if currentDate != nil {
FooterButtonView("retirer l'horaire") {
currentDate = nil
}
} footer: {
if confirmScheduleUpdate && updatingInProgress == false {
FooterButtonView("non, ne pas modifier les horaires") {
confirmScheduleUpdate = false
}
}
}
.font(.subheadline)
.buttonStyle(.borderless)
.onChange(of: startDate) {
dateUpdated = true
.headerProminence(.increased)
.onChange(of: matchFormat) {
confirmScheduleUpdate = true
}
}
}

@ -12,7 +12,6 @@ struct GroupStageScheduleEditorView: View {
@Bindable var groupStage: GroupStage
var tournament: Tournament
@State private var startDate: Date
@State private var uuid: UUID = UUID()
init(groupStage: GroupStage, tournament: Tournament) {
self.groupStage = groupStage
@ -21,24 +20,12 @@ struct GroupStageScheduleEditorView: View {
}
var body: some View {
Section {
//MatchFormatPickerView(headerLabel: "Format", matchFormat: $groupStage.matchFormat)
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
} header: {
Text(groupStage.groupStageTitle())
} footer: {
DateUpdateManagerView(startDate: $startDate, currentDate: $groupStage.startDate, duration: groupStage.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
groupStage.startDate = startDate
_save()
uuid = UUID()
}
DatePickingView(title: groupStage.groupStageTitle(), startDate: $startDate, currentDate: $groupStage.startDate, duration: groupStage.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
groupStage.startDate = startDate
_save()
}
.id(uuid)
}
private func _save() {
try? dataStore.groupStages.addOrUpdate(instance: groupStage)
}

@ -9,71 +9,66 @@ import SwiftUI
struct LoserRoundScheduleEditorView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
var upperRound: Round
var tournament: Tournament
var loserRounds: [Round]
@State private var startDate: Date
@State private var matchFormat: MatchFormat
init(upperRound: Round) {
init(upperRound: Round, tournament: Tournament) {
self.upperRound = upperRound
self.tournament = tournament
let _loserRounds = upperRound.loserRounds()
self.loserRounds = _loserRounds
self._startDate = State(wrappedValue: _loserRounds.first(where: { $0.startDate != nil })?.startDate ?? _loserRounds.first(where: { $0.isDisabled() == false })?.enabledMatches().first?.startDate ?? Date())
self._startDate = State(wrappedValue: _loserRounds.first(where: { $0.startDate != nil })?.startDate ?? _loserRounds.first(where: { $0.isDisabled() == false })?.enabledMatches().first?.startDate ?? tournament.startDate)
self._matchFormat = State(wrappedValue: _loserRounds.first?.matchFormat ?? upperRound.matchFormat)
}
var body: some View {
List {
Section {
MatchFormatPickerView(headerLabel: "Format", matchFormat: $matchFormat)
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
} header: {
Text("Match de classement " + upperRound.roundTitle())
} footer: {
DateUpdateManagerView(startDate: $startDate, currentDate: .constant(nil), duration: matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
MatchFormatPickingView(matchFormat: $matchFormat) {
await _updateSchedule()
}
DatePickingView(title: "Horaire minimum", startDate: $startDate, currentDate: .constant(nil), duration: matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
await _updateSchedule()
}
let enabledLoserRounds = upperRound.loserRounds().filter({ $0.isDisabled() == false })
ForEach(enabledLoserRounds.indices, id: \.self) { index in
let loserRound = enabledLoserRounds[index]
LoserRoundStepScheduleEditorView(stepIndex: index, round: loserRound, upperRound: upperRound)
LoserRoundStepScheduleEditorView(stepIndex: index, round: loserRound, upperRound: upperRound, tournament: tournament)
.id(UUID())
}
}
.onChange(of: matchFormat) {
loserRounds.forEach { round in
round.updateMatchFormatAndAllMatches(matchFormat)
//round.updateIfRequiredMatchFormat(matchFormat, andLoserBracket: true)
round.updateMatchFormat(matchFormat, checkIfPossible: false, andLoserBracket: true)
}
_save()
}
.headerProminence(.increased)
.navigationTitle("Horaires")
.navigationTitle("Classement " + upperRound.roundTitle())
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
}
private func _updateSchedule() {
let matches = upperRound.loserRounds().flatMap({ round in
round.playedMatches()
})
upperRound.loserRounds().forEach({ round in
round.resetFromRoundAllMatchesStartDate()
})
private func _updateSchedule() async {
// let matches = upperRound.loserRounds().flatMap({ round in
// round.playedMatches()
// })
// upperRound.loserRounds().forEach({ round in
// round.resetFromRoundAllMatchesStartDate()
// })
//
// try? dataStore.matches.addOrUpdate(contentOfs: matches)
// _save()
try? dataStore.matches.addOrUpdate(contentOfs: matches)
let loserRounds = upperRound.loserRounds().filter { $0.isDisabled() == false }
MatchScheduler.shared.updateBracketSchedule(tournament: tournament, fromRoundId: loserRounds.first?.id, fromMatchId: nil, startDate: startDate)
loserRounds.first?.startDate = startDate
_save()
MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: upperRound.loserRounds().first?.id, fromMatchId: nil, startDate: startDate)
_save()
upperRound.loserRounds().first?.startDate = startDate
}
private func _save() {

@ -9,75 +9,57 @@ import SwiftUI
struct LoserRoundStepScheduleEditorView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
var stepIndex: Int
var round: Round
var upperRound: Round
var tournament: Tournament
var matches: [Match]
@State private var startDate: Date
@State private var matchFormat: MatchFormat
//@State private var matchFormat: MatchFormat
init(stepIndex: Int, round: Round, upperRound: Round) {
init(stepIndex: Int, round: Round, upperRound: Round, tournament: Tournament) {
self.upperRound = upperRound
self.tournament = tournament
self.round = round
self.stepIndex = stepIndex
let _matches = upperRound.loserRounds(forRoundIndex: round.index).flatMap({ $0.enabledMatches() })
self.matches = _matches
self._startDate = State(wrappedValue: round.startDate ?? _matches.first?.startDate ?? Date())
self._matchFormat = State(wrappedValue: round.matchFormat)
self._startDate = State(wrappedValue: round.startDate ?? _matches.first?.startDate ?? tournament.startDate)
//self._matchFormat = State(wrappedValue: round.matchFormat)
}
var body: some View {
@Bindable var round = round
DatePickingView(title: "Tour #\(stepIndex + 1)", startDate: $startDate, currentDate: .constant(nil), duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
await _updateSchedule()
}
Section {
MatchFormatPickerView(headerLabel: "Format", matchFormat: $matchFormat)
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
NavigationLink {
List {
ForEach(matches) { match in
if match.disabled == false {
MatchScheduleEditorView(match: match)
MatchScheduleEditorView(match: match, tournament: tournament)
}
}
}
.headerProminence(.increased)
.navigationTitle(round.selectionLabel())
.navigationTitle("Tour #\(stepIndex + 1)")
.environment(tournament)
} label: {
Text("Voir tous les matchs")
}
} header: {
Text("Tour #\(stepIndex + 1)")
} footer: {
HStack {
DateUpdateManagerView(startDate: $startDate, currentDate: .constant(nil), duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
Text("Voir tous les matchs du \((stepIndex + 1).ordinalFormatted()) tour")
}
}
.headerProminence(.increased)
.onChange(of: matchFormat) {
round.updateMatchFormatAndAllMatches(matchFormat)
_save()
}
.onChange(of: round.startDate) {
_save()
}
}
private func _updateSchedule() {
upperRound.loserRounds(forRoundIndex: round.index).forEach({ round in
round.resetFromRoundAllMatchesStartDate()
})
try? dataStore.matches.addOrUpdate(contentOfs: matches)
_save()
MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate)
private func _updateSchedule() async {
MatchScheduler.shared.updateBracketSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate)
upperRound.loserRounds(forRoundIndex: round.index).forEach({ round in
round.startDate = startDate
})

@ -8,39 +8,35 @@
import SwiftUI
struct MatchScheduleEditorView: View {
@Environment(Tournament.self) var tournament: Tournament
@Bindable var match: Match
var tournament: Tournament
@State private var startDate: Date
init(match: Match) {
init(match: Match, tournament: Tournament) {
self.match = match
self._startDate = State(wrappedValue: match.startDate ?? Date())
self.tournament = tournament
self._startDate = State(wrappedValue: match.startDate ?? tournament.startDate)
}
var title: String {
if let round = match.roundObject {
return round.roundTitle() + " " + match.matchTitle()
} else {
return match.matchTitle()
}
}
var body: some View {
Section {
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
} header: {
if let round = match.roundObject {
Text(round.roundTitle() + " " + match.matchTitle())
} else {
Text(match.matchTitle())
}
} footer: {
DateUpdateManagerView(startDate: $startDate, currentDate: .constant(nil), duration: match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
DatePickingView(title: title, startDate: $startDate, currentDate: .constant(nil), duration: match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
await _updateSchedule()
}
.headerProminence(.increased)
}
private func _updateSchedule() {
MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: match.round, fromMatchId: match.id, startDate: startDate)
private func _updateSchedule() async {
MatchScheduler.shared.updateBracketSchedule(tournament: tournament, fromRoundId: match.round, fromMatchId: match.id, startDate: startDate)
}
}
#Preview {
MatchScheduleEditorView(match: Match.mock())
MatchScheduleEditorView(match: Match.mock(), tournament: Tournament.mock())
}

@ -179,11 +179,9 @@ struct PlanningSettingsView: View {
}
private func _setupSchedule() async {
let groupStageCourtCount = tournament.groupStageCourtCount ?? 1
let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
let matchScheduler = MatchScheduler.shared
matchScheduler.courtsUnavailability = tournament.eventObject?.courtsUnavailability
matchScheduler.options.removeAll()
if shouldEndBeforeStartNext {
@ -214,35 +212,7 @@ struct PlanningSettingsView: View {
matchScheduler.upperBracketRotationDifference = upperBracketRotationDifference
matchScheduler.timeDifferenceLimit = timeDifferenceLimit
let matches = tournament.groupStages().flatMap({ $0._matches() })
matches.forEach({
$0.removeCourt()
$0.startDate = nil
})
var lastDate : Date = tournament.startDate
groupStages.chunked(into: groupStageCourtCount).forEach { groups in
groups.forEach({ $0.startDate = lastDate })
try? dataStore.groupStages.addOrUpdate(contentOfs: groups)
let dispatch = matchScheduler.groupStageDispatcher(numberOfCourtsAvailablePerRotation: numberOfCourtsAvailablePerRotation, groupStages: groups, startingDate: lastDate)
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60)
}
match.setCourt(matchSchedule.courtIndex)
}
}
}
try? dataStore.matches.addOrUpdate(contentOfs: matches)
matchScheduler.updateSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate)
matchScheduler.updateSchedule(tournament: tournament)
}
private func _save() {

@ -12,12 +12,14 @@ struct PlanningView: View {
@Environment(Tournament.self) var tournament: Tournament
let matches: [Match]
@Binding var selectedScheduleDestination: ScheduleDestination?
@State private var timeSlots: [Date:[Match]]
@State private var days: [Date]
@State private var keys: [Date]
init(matches: [Match]) {
init(matches: [Match], selectedScheduleDestination: Binding<ScheduleDestination?>) {
self.matches = matches
_selectedScheduleDestination = selectedScheduleDestination
let timeSlots = Dictionary(grouping: matches) { $0.startDate ?? .distantFuture }
_timeSlots = State(wrappedValue: timeSlots)
_days = State(wrappedValue: Set(timeSlots.keys.map { $0.startOfDay }).sorted())
@ -26,43 +28,58 @@ struct PlanningView: View {
var body: some View {
List {
ForEach(days, id: \.self) { day in
Section {
ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in
if let _matches = timeSlots[key] {
DisclosureGroup {
ForEach(_matches) { match in
NavigationLink {
MatchDetailView(match: match, matchViewStyle: .sectionedStandardStyle)
} label: {
LabeledContent {
if let courtName = match.courtName() {
Text(courtName)
}
if matches.allSatisfy({ $0.startDate == nil }) == false {
ForEach(days, id: \.self) { day in
Section {
ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in
if let _matches = timeSlots[key] {
DisclosureGroup {
ForEach(_matches) { match in
NavigationLink {
MatchDetailView(match: match, matchViewStyle: .sectionedStandardStyle)
} label: {
if let groupStage = match.groupStageObject {
Text(groupStage.groupStageTitle())
} else if let round = match.roundObject {
Text(round.roundTitle())
LabeledContent {
if let courtName = match.courtName() {
Text(courtName)
}
} label: {
if let groupStage = match.groupStageObject {
Text(groupStage.groupStageTitle())
} else if let round = match.roundObject {
Text(round.roundTitle())
}
Text(match.matchTitle())
}
Text(match.matchTitle())
}
}
} label: {
_timeSlotView(key: key, matches: _matches)
}
} label: {
_timeSlotView(key: key, matches: _matches)
}
}
} header: {
HStack {
Text(day.formatted(.dateTime.day().weekday().month()))
Spacer()
let count = _matchesCount(inDayInt: day.dayInt)
Text(count.formatted() + " match" + count.pluralSuffix)
}
}
} header: {
HStack {
Text(day.formatted(.dateTime.day().weekday().month()))
Spacer()
let count = _matchesCount(inDayInt: day.dayInt)
Text(count.formatted() + " match" + count.pluralSuffix)
.headerProminence(.increased)
}
}
}
.overlay {
if matches.allSatisfy({ $0.startDate == nil }) {
ContentUnavailableView {
Label("Aucun horaire défini", systemImage: "clock.badge.questionmark")
} description: {
Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi")
} actions: {
RowButtonView("Horaire intelligent") {
selectedScheduleDestination = nil
}
}
.headerProminence(.increased)
}
}
.navigationTitle("Programmation")
@ -83,5 +100,5 @@ struct PlanningView: View {
}
#Preview {
PlanningView(matches: [])
PlanningView(matches: [], selectedScheduleDestination: .constant(nil))
}

@ -9,32 +9,30 @@ import SwiftUI
struct RoundScheduleEditorView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
var round: Round
var tournament: Tournament
@State private var startDate: Date
init(round: Round) {
init(round: Round, tournament: Tournament) {
self.round = round
self._startDate = State(wrappedValue: round.startDate ?? round.playedMatches().first?.startDate ?? Date())
self.tournament = tournament
self._startDate = State(wrappedValue: round.startDate ?? round.playedMatches().first?.startDate ?? tournament.startDate)
}
var body: some View {
@Bindable var round = round
List {
Section {
MatchFormatPickerView(headerLabel: "Format", matchFormat: $round.matchFormat)
DatePicker(selection: $startDate) {
Text(startDate.formatted(.dateTime.weekday(.wide))).font(.headline)
}
} footer: {
DateUpdateManagerView(startDate: $startDate, currentDate: $round.startDate, duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
MatchFormatPickingView(matchFormat: $round.matchFormat) {
await _updateSchedule()
}
DatePickingView(title: "Horaire minimum", startDate: $startDate, currentDate: $round.startDate, duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
await _updateSchedule()
}
ForEach(round.playedMatches()) { match in
MatchScheduleEditorView(match: match)
MatchScheduleEditorView(match: match, tournament: tournament)
.id(UUID())
}
}
@ -48,22 +46,15 @@ struct RoundScheduleEditorView: View {
round.updateMatchFormatOfAllMatches(round.matchFormat)
for index in (0..<round.index).reversed() {
if let upperRound = upperRounds.filter({ $0.index == index }).first {
upperRound.updateIfRequiredMatchFormat(round.matchFormat, andLoserBracket: false)
upperRound.updateMatchFormat(round.matchFormat, checkIfPossible: true, andLoserBracket: false)
}
}
_save()
}
}
private func _updateSchedule() {
let matches = round._matches()
matches.forEach { match in
match.matchFormat = round.matchFormat
}
try? dataStore.matches.addOrUpdate(contentOfs: matches)
_save()
MatchScheduler.shared.updateSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate)
private func _updateSchedule() async {
MatchScheduler.shared.updateBracketSchedule(tournament: tournament, fromRoundId: round.id, fromMatchId: nil, startDate: startDate)
round.startDate = startDate
_save()
}
@ -74,5 +65,5 @@ struct RoundScheduleEditorView: View {
}
#Preview {
RoundScheduleEditorView(round: Round.mock())
RoundScheduleEditorView(round: Round.mock(), tournament: Tournament.mock())
}

@ -22,30 +22,27 @@ struct SchedulerView: View {
@EnvironmentObject var dataStore: DataStore
var tournament: Tournament
var destination: ScheduleDestination
var body: some View {
@Bindable var tournament = tournament
List {
switch destination {
case .scheduleGroupStage:
Section {
MatchFormatPickerView(headerLabel: "Format", matchFormat: $tournament.groupStageMatchFormat)
.onChange(of: tournament.groupStageMatchFormat) {
let groupStages = tournament.groupStages()
groupStages.forEach { groupStage in
groupStage.updateMatchFormat(tournament.groupStageMatchFormat)
}
try? dataStore.tournaments.addOrUpdate(instance: tournament)
try? dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
}
// } footer: {
// if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight {
// FooterButtonView("passer en " + tournament.groupStageSmartMatchFormat().format) {
// tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat()
// }
// }
MatchFormatPickingView(matchFormat: $tournament.groupStageMatchFormat) {
Task {
MatchScheduler.shared.updateSchedule(tournament: tournament)
}
}
.onChange(of: tournament.groupStageMatchFormat) {
let groupStages = tournament.groupStages()
groupStages.forEach { groupStage in
groupStage.updateMatchFormat(tournament.groupStageMatchFormat)
}
try? dataStore.tournaments.addOrUpdate(instance: tournament)
try? dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
}
ForEach(tournament.groupStages()) {
GroupStageScheduleEditorView(groupStage: $0, tournament: tournament)
.id(UUID())
@ -59,36 +56,64 @@ struct SchedulerView: View {
}
}
.headerProminence(.increased)
.monospacedDigit()
}
@ViewBuilder
func _roundView(_ round: Round) -> some View {
Section {
NavigationLink {
RoundScheduleEditorView(round: round)
.environment(tournament)
RoundScheduleEditorView(round: round, tournament: tournament)
.navigationTitle(round.titleLabel())
.environment(tournament)
} label: {
LabeledContent {
Text(round.matchFormat.format).font(.largeTitle)
} label: {
if let startDate = round.getStartDate() {
Text(startDate.formatted(.dateTime.hour().minute())).font(.largeTitle)
Text(startDate.formatted(.dateTime.weekday().day(.twoDigits).month().year()))
HStack {
Text(startDate.formattedAsHourMinute()).font(.largeTitle)
if let estimatedEndDate = round.estimatedEndDate(tournament.additionalEstimationDuration) {
Image(systemName: "arrowshape.forward.fill")
Text(estimatedEndDate.formattedAsHourMinute()).font(.largeTitle)
}
}
Text(startDate.formattedAsDate())
} else {
Text("Aucun horaire")
}
}
}
} header: {
Text(round.titleLabel())
}
Section {
NavigationLink {
LoserRoundScheduleEditorView(upperRound: round)
LoserRoundScheduleEditorView(upperRound: round, tournament: tournament)
.environment(tournament)
} label: {
Text("Match de classement \(round.roundTitle(.short))")
LabeledContent {
let count = round.loserRounds().filter({ $0.isDisabled() == false }).count
Text(count.formatted() + " tour" + count.pluralSuffix)
} label: {
if let startDate = round.getLoserRoundStartDate() {
HStack {
Text(startDate.formattedAsHourMinute()).font(.title3)
if let estimatedEndDate = round.estimatedLoserRoundEndDate(tournament.additionalEstimationDuration) {
Image(systemName: "arrowshape.forward.fill")
Text(estimatedEndDate.formattedAsHourMinute()).font(.title3)
}
}
Text(startDate.formattedAsDate())
} else {
Text("Aucun horaire")
}
}
}
} header: {
Text(round.titleLabel())
Text("Match de classement \(round.roundTitle(.short))")
}
.headerProminence(.increased)
}
}

@ -34,7 +34,7 @@ enum ScheduleDestination: String, Identifiable, Selectable {
case .scheduleBracket:
return "Tableau"
case .planning:
return "Programmation"
return "Prog."
}
}
@ -50,7 +50,7 @@ enum ScheduleDestination: String, Identifiable, Selectable {
struct TournamentScheduleView: View {
var tournament: Tournament
@State private var selectedScheduleDestination: ScheduleDestination? = nil
@State private var selectedScheduleDestination: ScheduleDestination? = .planning
let allDestinations: [ScheduleDestination]
init(tournament: Tournament) {
@ -79,7 +79,7 @@ struct TournamentScheduleView: View {
case .scheduleBracket:
SchedulerView(tournament: tournament, destination: selectedSchedule)
case .planning:
PlanningView(matches: tournament.allMatches())
PlanningView(matches: tournament.allMatches(), selectedScheduleDestination: $selectedScheduleDestination)
}
}
}

Loading…
Cancel
Save