fix scheduler stuff

add open organizer stuff
multistore
Razmig Sarkissian 2 years ago
parent cf44ce1310
commit cf0259e50b
  1. 12
      PadelClub/Data/MatchScheduler.swift
  2. 9
      PadelClub/Data/Tournament.swift
  3. 17
      PadelClub/ViewModel/NavigationViewModel.swift
  4. 2
      PadelClub/ViewModel/SearchViewModel.swift
  5. 2
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  6. 18
      PadelClub/Views/Navigation/Organizer/TournamentButtonView.swift
  7. 18
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  8. 16
      PadelClub/Views/Planning/PlanningSettingsView.swift
  9. 3
      PadelClubTests/ServerDataTests.swift

@ -26,7 +26,8 @@ class MatchScheduler : ModelObject, Storable {
var rotationDifferenceIsImportant: Bool var rotationDifferenceIsImportant: Bool
var shouldHandleUpperRoundSlice: Bool var shouldHandleUpperRoundSlice: Bool
var shouldEndRoundBeforeStartingNext: Bool var shouldEndRoundBeforeStartingNext: Bool
var groupStageChunkCount: Int?
init(tournament: String, init(tournament: String,
timeDifferenceLimit: Int = 5, timeDifferenceLimit: Int = 5,
loserBracketRotationDifference: Int = 0, loserBracketRotationDifference: Int = 0,
@ -36,7 +37,8 @@ class MatchScheduler : ModelObject, Storable {
randomizeCourts: Bool = true, randomizeCourts: Bool = true,
rotationDifferenceIsImportant: Bool = false, rotationDifferenceIsImportant: Bool = false,
shouldHandleUpperRoundSlice: Bool = true, shouldHandleUpperRoundSlice: Bool = true,
shouldEndRoundBeforeStartingNext: Bool = true) { shouldEndRoundBeforeStartingNext: Bool = true,
groupStageChunkCount: Int? = nil) {
self.tournament = tournament self.tournament = tournament
self.timeDifferenceLimit = timeDifferenceLimit self.timeDifferenceLimit = timeDifferenceLimit
self.loserBracketRotationDifference = loserBracketRotationDifference self.loserBracketRotationDifference = loserBracketRotationDifference
@ -47,6 +49,7 @@ class MatchScheduler : ModelObject, Storable {
self.rotationDifferenceIsImportant = rotationDifferenceIsImportant self.rotationDifferenceIsImportant = rotationDifferenceIsImportant
self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice
self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext
self.groupStageChunkCount = groupStageChunkCount
} }
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
@ -61,6 +64,7 @@ class MatchScheduler : ModelObject, Storable {
case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant"
case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice"
case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext"
case _groupStageChunkCount = "groupStageChunkCount"
} }
var courtsUnavailability: [DateInterval]? { var courtsUnavailability: [DateInterval]? {
@ -77,7 +81,7 @@ class MatchScheduler : ModelObject, Storable {
@discardableResult @discardableResult
func updateGroupStageSchedule(tournament: Tournament) -> Date { func updateGroupStageSchedule(tournament: Tournament) -> Date {
let groupStageCourtCount = tournament.groupStageCourtCount ?? 1 let computedGroupStageChunkCount = groupStageChunkCount ?? 1
let groupStages = tournament.groupStages() let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
@ -116,7 +120,7 @@ class MatchScheduler : ModelObject, Storable {
} }
}) })
groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: groupStageCourtCount).forEach { groups in groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: computedGroupStageChunkCount).forEach { groups in
groups.forEach({ $0.startDate = lastDate }) groups.forEach({ $0.startDate = lastDate })
try? DataStore.shared.groupStages.addOrUpdate(contentOfs: groups) try? DataStore.shared.groupStages.addOrUpdate(contentOfs: groups)

@ -32,7 +32,6 @@ class Tournament : ModelObject, Storable {
var federalCategory: TournamentCategory var federalCategory: TournamentCategory
var federalLevelCategory: TournamentLevel var federalLevelCategory: TournamentLevel
var federalAgeCategory: FederalTournamentAge var federalAgeCategory: FederalTournamentAge
var groupStageCourtCount: Int?
var closedRegistrationDate: Date? var closedRegistrationDate: Date?
var groupStageAdditionalQualified: Int var groupStageAdditionalQualified: Int
var courtCount: Int = 2 var courtCount: Int = 2
@ -74,7 +73,6 @@ class Tournament : ModelObject, Storable {
case _federalCategory = "federalCategory" case _federalCategory = "federalCategory"
case _federalLevelCategory = "federalLevelCategory" case _federalLevelCategory = "federalLevelCategory"
case _federalAgeCategory = "federalAgeCategory" case _federalAgeCategory = "federalAgeCategory"
case _groupStageCourtCount = "groupStageCourtCount"
case _seedCount = "seedCount" case _seedCount = "seedCount"
case _closedRegistrationDate = "closedRegistrationDate" case _closedRegistrationDate = "closedRegistrationDate"
case _groupStageAdditionalQualified = "groupStageAdditionalQualified" case _groupStageAdditionalQualified = "groupStageAdditionalQualified"
@ -94,7 +92,7 @@ class Tournament : ModelObject, Storable {
case _publishBrackets = "publishBrackets" case _publishBrackets = "publishBrackets"
} }
internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, groupStageCourtCount: Int? = nil, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false) { internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false) {
self.event = event self.event = event
self.name = name self.name = name
self.startDate = startDate self.startDate = startDate
@ -113,7 +111,6 @@ class Tournament : ModelObject, Storable {
self.federalCategory = federalCategory self.federalCategory = federalCategory
self.federalLevelCategory = federalLevelCategory self.federalLevelCategory = federalLevelCategory
self.federalAgeCategory = federalAgeCategory self.federalAgeCategory = federalAgeCategory
self.groupStageCourtCount = groupStageCourtCount
self.closedRegistrationDate = closedRegistrationDate self.closedRegistrationDate = closedRegistrationDate
self.groupStageAdditionalQualified = groupStageAdditionalQualified self.groupStageAdditionalQualified = groupStageAdditionalQualified
self.courtCount = courtCount self.courtCount = courtCount
@ -150,7 +147,6 @@ class Tournament : ModelObject, Storable {
federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory) federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory)
federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory) federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory)
federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory) federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory)
groupStageCourtCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCourtCount)
closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate)
groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified) groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified)
courtCount = try container.decode(Int.self, forKey: ._courtCount) courtCount = try container.decode(Int.self, forKey: ._courtCount)
@ -222,7 +218,6 @@ class Tournament : ModelObject, Storable {
try container.encode(federalCategory, forKey: ._federalCategory) try container.encode(federalCategory, forKey: ._federalCategory)
try container.encode(federalLevelCategory, forKey: ._federalLevelCategory) try container.encode(federalLevelCategory, forKey: ._federalLevelCategory)
try container.encode(federalAgeCategory, forKey: ._federalAgeCategory) try container.encode(federalAgeCategory, forKey: ._federalAgeCategory)
try container.encodeIfPresent(groupStageCourtCount, forKey: ._groupStageCourtCount)
try container.encodeIfPresent(closedRegistrationDate, forKey: ._closedRegistrationDate) try container.encodeIfPresent(closedRegistrationDate, forKey: ._closedRegistrationDate)
try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified)
try container.encode(courtCount, forKey: ._courtCount) try container.encode(courtCount, forKey: ._courtCount)
@ -1637,7 +1632,7 @@ extension Tournament {
} }
static func fake() -> Tournament { static func fake() -> Tournament {
return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
} }
} }

@ -16,4 +16,21 @@ class NavigationViewModel {
var selectedTab: TabDestination? var selectedTab: TabDestination?
var agendaDestination: AgendaDestination? = .activity var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament? var tournament: Tournament?
var organizerTournament: Tournament?
func isTournamentAlreadyOpenInOrganizer(_ tournament: Tournament) -> Bool {
organizerTournament?.id == tournament.id
}
func closeTournamentFromOrganizer(_ tournament: Tournament) {
tournament.navigationPath.removeAll()
organizerTournament = nil
}
func openTournamentInOrganizer(_ tournament: Tournament) {
organizerTournament = tournament
if selectedTab != .tournamentOrganizer {
selectedTab = .tournamentOrganizer
}
}
} }

@ -69,7 +69,7 @@ class SearchViewModel: ObservableObject, Identifiable {
} }
func codeClubs() -> [String] { func codeClubs() -> [String] {
DataStore.shared.clubs.compactMap { $0.code } DataStore.shared.user.clubsObjects().compactMap { $0.code }
} }
func getCodeClub() -> String? { func getCodeClub() -> String? {

@ -103,7 +103,7 @@ struct EventListView: View {
} }
.contextMenu { .contextMenu {
Button { Button {
navigation.openTournamentInOrganizer(tournament)
} label: { } label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow") Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
} }

@ -8,21 +8,15 @@
import SwiftUI import SwiftUI
struct TournamentButtonView: View { struct TournamentButtonView: View {
@Environment(NavigationViewModel.self) private var navigation
let tournament: Tournament let tournament: Tournament
@Binding var selectedId: String?
var body: some View { var body: some View {
Button { Button {
if selectedId == tournament.id { if navigation.isTournamentAlreadyOpenInOrganizer(tournament) {
tournament.navigationPath.removeAll() navigation.closeTournamentFromOrganizer(tournament)
selectedId = nil
// if tournament.navigationPath.isEmpty {
// selectedId = nil
// } else {
// tournament.navigationPath.removeLast()
// }
} else { } else {
selectedId = tournament.id navigation.openTournamentInOrganizer(tournament)
} }
} label: { } label: {
TournamentCellView(tournament: tournament, displayStyle: .short) TournamentCellView(tournament: tournament, displayStyle: .short)
@ -34,7 +28,7 @@ struct TournamentButtonView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
.overlay(alignment: .top) { .overlay(alignment: .top) {
if selectedId == tournament.id { if navigation.isTournamentAlreadyOpenInOrganizer(tournament) {
Image(systemName: "ellipsis") Image(systemName: "ellipsis")
.offset(y: -10) .offset(y: -10)
} }

@ -10,17 +10,13 @@ import LeStorage
struct TournamentOrganizerView: View { struct TournamentOrganizerView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State private var selectedTournamentId: String? @Environment(NavigationViewModel.self) private var navigation
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(dataStore.tournaments) { tournament in if let tournament = navigation.organizerTournament {
if tournament.id == selectedTournamentId { OrganizedTournamentView(tournament: tournament)
OrganizedTournamentView(tournament: tournament) } else {
}
}
if selectedTournamentId == nil {
NavigationStack { NavigationStack {
let userClubsEmpty = dataStore.user.clubs.isEmpty let userClubsEmpty = dataStore.user.clubs.isEmpty
ContentUnavailableView( ContentUnavailableView(
@ -39,7 +35,7 @@ struct TournamentOrganizerView: View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack { HStack {
ForEach(dataStore.tournaments) { tournament in ForEach(dataStore.tournaments) { tournament in
TournamentButtonView(tournament: tournament, selectedId: $selectedTournamentId) TournamentButtonView(tournament: tournament)
} }
} }
.padding() .padding()
@ -48,7 +44,7 @@ struct TournamentOrganizerView: View {
} }
} }
.onChange(of: Store.main.currentUserUUID) { .onChange(of: Store.main.currentUserUUID) {
selectedTournamentId = nil navigation.organizerTournament = nil
} }
} }
} }

@ -13,7 +13,7 @@ struct PlanningSettingsView: View {
@Bindable var tournament: Tournament @Bindable var tournament: Tournament
@Bindable var matchScheduler: MatchScheduler @Bindable var matchScheduler: MatchScheduler
@State private var groupStageCourtCount: Int @State private var groupStageChunkCount: Int
@State private var isScheduling: Bool = false @State private var isScheduling: Bool = false
@State private var schedulingDone: Bool = false @State private var schedulingDone: Bool = false
@State private var showOptions: Bool = false @State private var showOptions: Bool = false
@ -22,10 +22,11 @@ struct PlanningSettingsView: View {
self.tournament = tournament self.tournament = tournament
if let matchScheduler = tournament.matchScheduler() { if let matchScheduler = tournament.matchScheduler() {
self.matchScheduler = matchScheduler self.matchScheduler = matchScheduler
self._groupStageChunkCount = State(wrappedValue: matchScheduler.groupStageChunkCount ?? 1)
} else { } else {
self.matchScheduler = MatchScheduler(tournament: tournament.id) self.matchScheduler = MatchScheduler(tournament: tournament.id)
self._groupStageChunkCount = State(wrappedValue: 1)
} }
self._groupStageCourtCount = State(wrappedValue: tournament.groupStageCourtCount ?? 1)
} }
var body: some View { var body: some View {
@ -48,7 +49,7 @@ struct PlanningSettingsView: View {
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount)
if tournament.groupStages().isEmpty == false { if tournament.groupStages().isEmpty == false {
TournamentFieldsManagerView(localizedStringKey: "Nombre de poule en même temps", count: $groupStageCourtCount, max: tournament.groupStageCount) TournamentFieldsManagerView(localizedStringKey: "Nombre de poule en même temps", count: $groupStageChunkCount, max: tournament.groupStageCount)
} }
if let event = tournament.eventObject() { if let event = tournament.eventObject() {
@ -73,6 +74,7 @@ struct PlanningSettingsView: View {
RowButtonView("Horaire intelligent", role: .destructive) { RowButtonView("Horaire intelligent", role: .destructive) {
schedulingDone = false schedulingDone = false
await _setupSchedule() await _setupSchedule()
_save()
schedulingDone = true schedulingDone = true
} }
} footer: { } footer: {
@ -114,9 +116,8 @@ struct PlanningSettingsView: View {
.deferredRendering(for: .seconds(2)) .deferredRendering(for: .seconds(2))
} }
} }
.onChange(of: groupStageCourtCount) { .onChange(of: groupStageChunkCount) {
tournament.groupStageCourtCount = groupStageCourtCount matchScheduler.groupStageChunkCount = groupStageChunkCount
_save()
} }
.onChange(of: tournament.startDate) { .onChange(of: tournament.startDate) {
_save() _save()
@ -124,9 +125,6 @@ struct PlanningSettingsView: View {
.onChange(of: tournament.courtCount) { .onChange(of: tournament.courtCount) {
_save() _save()
} }
.onChange(of: tournament.groupStageCourtCount) {
_save()
}
.onChange(of: tournament.dayDuration) { .onChange(of: tournament.dayDuration) {
_save() _save()
} }

@ -96,7 +96,7 @@ final class ServerDataTests: XCTestCase {
return return
} }
let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, groupStageCourtCount: 6, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true) let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true)
let t = try await Store.main.service().post(tournament) let t = try await Store.main.service().post(tournament)
assert(t.event == tournament.event) assert(t.event == tournament.event)
@ -117,7 +117,6 @@ final class ServerDataTests: XCTestCase {
assert(t.federalCategory == tournament.federalCategory) assert(t.federalCategory == tournament.federalCategory)
assert(t.federalLevelCategory == tournament.federalLevelCategory) assert(t.federalLevelCategory == tournament.federalLevelCategory)
assert(t.federalAgeCategory == tournament.federalAgeCategory) assert(t.federalAgeCategory == tournament.federalAgeCategory)
assert(t.groupStageCourtCount == tournament.groupStageCourtCount)
assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted()) assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted())
assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified) assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified)
assert(t.courtCount == tournament.courtCount) assert(t.courtCount == tournament.courtCount)

Loading…
Cancel
Save