|
|
|
|
@ -10,7 +10,7 @@ import LeStorage |
|
|
|
|
import PadelClubData |
|
|
|
|
|
|
|
|
|
struct TableStructureView: View { |
|
|
|
|
@Environment(Tournament.self) private var tournament: Tournament |
|
|
|
|
var tournament: Tournament |
|
|
|
|
@EnvironmentObject private var dataStore: DataStore |
|
|
|
|
@Environment(\.dismiss) var dismiss |
|
|
|
|
@State private var presentRefreshStructureWarning: Bool = false |
|
|
|
|
@ -24,7 +24,10 @@ struct TableStructureView: View { |
|
|
|
|
@State private var buildWildcards: Bool = true |
|
|
|
|
@FocusState private var stepperFieldIsFocused: Bool |
|
|
|
|
@State private var confirmReset: Bool = false |
|
|
|
|
|
|
|
|
|
@State private var selectedTournament: Tournament? |
|
|
|
|
@State private var initialSeedCount: Int = 0 |
|
|
|
|
@State private var initialSeedRound: Int = 0 |
|
|
|
|
|
|
|
|
|
func displayWarning() -> Bool { |
|
|
|
|
let unsortedTeamsCount = tournament.unsortedTeamsCount() |
|
|
|
|
return tournament.shouldWarnOnlineRegistrationUpdates() && teamCount != tournament.teamCount && (tournament.teamCount <= unsortedTeamsCount || teamCount <= unsortedTeamsCount) |
|
|
|
|
@ -60,9 +63,18 @@ struct TableStructureView: View { |
|
|
|
|
var tsPure: Int { |
|
|
|
|
max(teamCount - groupStageCount * teamsPerGroupStage, 0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ViewBuilder |
|
|
|
|
|
|
|
|
|
init(tournament: Tournament) { |
|
|
|
|
self.tournament = tournament |
|
|
|
|
_teamCount = .init(wrappedValue: tournament.teamCount) |
|
|
|
|
_groupStageCount = .init(wrappedValue: tournament.groupStageCount) |
|
|
|
|
_teamsPerGroupStage = .init(wrappedValue: tournament.teamsPerGroupStage) |
|
|
|
|
_qualifiedPerGroupStage = .init(wrappedValue: tournament.qualifiedPerGroupStage) |
|
|
|
|
_groupStageAdditionalQualified = .init(wrappedValue: tournament.groupStageAdditionalQualified) |
|
|
|
|
_initialSeedCount = .init(wrappedValue: tournament.initialSeedCount) |
|
|
|
|
_initialSeedRound = .init(wrappedValue: tournament.initialSeedRound) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
List { |
|
|
|
|
if displayWarning() { |
|
|
|
|
@ -79,9 +91,33 @@ struct TableStructureView: View { |
|
|
|
|
} label: { |
|
|
|
|
Text("Préréglage") |
|
|
|
|
} |
|
|
|
|
.disabled(selectedTournament != nil) |
|
|
|
|
|
|
|
|
|
NavigationLink { |
|
|
|
|
TournamentSelectorView(selectedTournament: $selectedTournament) |
|
|
|
|
.environment(tournament) |
|
|
|
|
} label: { |
|
|
|
|
if let selectedTournament { |
|
|
|
|
TournamentCellView(tournament: selectedTournament, displayContext: .selection) |
|
|
|
|
} else { |
|
|
|
|
Text("À partir d'un tournoi existant") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
NavigationLink { |
|
|
|
|
HeadManagerView(initialSeedRound: $initialSeedRound, initialSeedCount: $initialSeedCount) |
|
|
|
|
.environment(tournament) |
|
|
|
|
} label: { |
|
|
|
|
Text("Configuration des têtes de série") |
|
|
|
|
} |
|
|
|
|
.disabled(selectedTournament != nil) |
|
|
|
|
|
|
|
|
|
} footer: { |
|
|
|
|
Text(structurePreset.localizedDescriptionStructurePresetTitle()) |
|
|
|
|
} |
|
|
|
|
.onChange(of: selectedTournament) { |
|
|
|
|
_updatePreset() |
|
|
|
|
} |
|
|
|
|
.onChange(of: structurePreset) { |
|
|
|
|
_updatePreset() |
|
|
|
|
} |
|
|
|
|
@ -226,7 +262,7 @@ struct TableStructureView: View { |
|
|
|
|
LabeledContent { |
|
|
|
|
Text(tsPure.formatted()) |
|
|
|
|
} label: { |
|
|
|
|
Text("Nombre de têtes de série") |
|
|
|
|
Text("Équipes à placer en tableau") |
|
|
|
|
|
|
|
|
|
if groupStageCount > 0 && tsPure > 0 && (tsPure > teamCount / 2 || tsPure < teamCount / 8 || tsPure < qualifiedFromGroupStage + groupStageAdditionalQualified) { |
|
|
|
|
Text("Attention !").foregroundStyle(.red) |
|
|
|
|
@ -235,7 +271,12 @@ struct TableStructureView: View { |
|
|
|
|
LabeledContent { |
|
|
|
|
Text(tf.formatted()) |
|
|
|
|
} label: { |
|
|
|
|
Text("Équipes en tableau final") |
|
|
|
|
Text("Effectif") |
|
|
|
|
} |
|
|
|
|
LabeledContent { |
|
|
|
|
Text(RoundRule.teamsInFirstRound(forTeams: tf).formatted()) |
|
|
|
|
} label: { |
|
|
|
|
Text("Dimension") |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
LabeledContent { |
|
|
|
|
@ -266,7 +307,7 @@ struct TableStructureView: View { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if tournament.rounds().isEmpty { |
|
|
|
|
if tournament.rounds().isEmpty && tournament.state() == .build { |
|
|
|
|
Section { |
|
|
|
|
RowButtonView("Ajouter un tableau", role: .destructive) { |
|
|
|
|
tournament.buildBracket(minimalBracketTeamCount: 4) |
|
|
|
|
@ -312,13 +353,6 @@ struct TableStructureView: View { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.toolbarBackground(.visible, for: .navigationBar) |
|
|
|
|
.onAppear { |
|
|
|
|
teamCount = tournament.teamCount |
|
|
|
|
groupStageCount = tournament.groupStageCount |
|
|
|
|
teamsPerGroupStage = tournament.teamsPerGroupStage |
|
|
|
|
qualifiedPerGroupStage = tournament.qualifiedPerGroupStage |
|
|
|
|
groupStageAdditionalQualified = tournament.groupStageAdditionalQualified |
|
|
|
|
} |
|
|
|
|
.onChange(of: teamCount) { |
|
|
|
|
if teamCount != tournament.teamCount { |
|
|
|
|
updatedElements.insert(.teamCount) |
|
|
|
|
@ -490,12 +524,79 @@ struct TableStructureView: View { |
|
|
|
|
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified |
|
|
|
|
|
|
|
|
|
if rebuildEverything { |
|
|
|
|
if let selectedTournament { |
|
|
|
|
tournament.matchFormat = selectedTournament.matchFormat |
|
|
|
|
tournament.groupStageMatchFormat = selectedTournament.groupStageMatchFormat |
|
|
|
|
tournament.loserBracketMatchFormat = selectedTournament.loserBracketMatchFormat |
|
|
|
|
tournament.initialSeedRound = selectedTournament.initialSeedRound |
|
|
|
|
tournament.initialSeedCount = selectedTournament.initialSeedCount |
|
|
|
|
} else { |
|
|
|
|
tournament.initialSeedRound = initialSeedRound |
|
|
|
|
tournament.initialSeedCount = initialSeedCount |
|
|
|
|
} |
|
|
|
|
tournament.removeWildCards() |
|
|
|
|
if structurePreset.hasWildcards(), buildWildcards { |
|
|
|
|
tournament.addWildCardIfNeeded(structurePreset.wildcardBrackets(), .bracket) |
|
|
|
|
tournament.addWildCardIfNeeded(structurePreset.wildcardQualifiers(), .groupStage) |
|
|
|
|
} |
|
|
|
|
tournament.deleteAndBuildEverything(preset: structurePreset) |
|
|
|
|
|
|
|
|
|
if let selectedTournament { |
|
|
|
|
let oldTournamentStart = selectedTournament.startDate |
|
|
|
|
let newTournamentStart = tournament.startDate |
|
|
|
|
let calendar = Calendar.current |
|
|
|
|
let oldComponents = calendar.dateComponents([.hour, .minute, .second], from: oldTournamentStart) |
|
|
|
|
var newComponents = calendar.dateComponents([.year, .month, .day], from: newTournamentStart) |
|
|
|
|
|
|
|
|
|
newComponents.hour = oldComponents.hour |
|
|
|
|
newComponents.minute = oldComponents.minute |
|
|
|
|
newComponents.second = oldComponents.second ?? 0 |
|
|
|
|
|
|
|
|
|
if let updatedStartDate = calendar.date(from: newComponents) { |
|
|
|
|
tournament.startDate = updatedStartDate |
|
|
|
|
} |
|
|
|
|
let allRounds = selectedTournament.rounds() |
|
|
|
|
let allRoundsNew = tournament.rounds() |
|
|
|
|
allRoundsNew.forEach { round in |
|
|
|
|
if let pRound = allRounds.first(where: { r in |
|
|
|
|
round.index == r.index |
|
|
|
|
}) { |
|
|
|
|
round.setData(from: pRound, tournamentStartDate: tournament.startDate, previousTournamentStartDate: oldTournamentStart) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let allGroupStages = selectedTournament.allGroupStages() |
|
|
|
|
let allGroupStagesNew = tournament.allGroupStages() |
|
|
|
|
allGroupStagesNew.forEach { groupStage in |
|
|
|
|
if let pGroupStage = allGroupStages.first(where: { gs in |
|
|
|
|
groupStage.index == gs.index |
|
|
|
|
}) { |
|
|
|
|
groupStage.setData(from: pGroupStage, tournamentStartDate: tournament.startDate, previousTournamentStartDate: oldTournamentStart) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tournament.tournamentStore?.matches.addOrUpdate(contentOfs: tournament._allMatchesIncludingDisabled()) |
|
|
|
|
} else { |
|
|
|
|
if initialSeedRound > 0 { |
|
|
|
|
if let round = tournament.rounds().first(where: { $0.index == initialSeedRound }) { |
|
|
|
|
let seedSorted = frenchUmpireOrder(for: RoundRule.numberOfMatches(forRoundIndex: round.index)) |
|
|
|
|
print(seedSorted) |
|
|
|
|
seedSorted.prefix(initialSeedCount).forEach { index in |
|
|
|
|
if let match = round._matches()[safe:index] { |
|
|
|
|
if match.indexInRound() < RoundRule.numberOfMatches(forRoundIndex: round.index) / 2 { |
|
|
|
|
match.previousMatch(.one)?.disableMatch() |
|
|
|
|
} else { |
|
|
|
|
match.previousMatch(.two)?.disableMatch() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if initialSeedCount > 0 { |
|
|
|
|
tournament.tournamentStore?.matches.addOrUpdate(contentOfs: tournament._allMatchesIncludingDisabled()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if (rebuildEverything == false && requirements.contains(.groupStage)) { |
|
|
|
|
tournament.deleteGroupStages() |
|
|
|
|
tournament.buildGroupStages() |
|
|
|
|
@ -515,12 +616,21 @@ struct TableStructureView: View { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _updatePreset() { |
|
|
|
|
teamCount = structurePreset.tableDimension() + structurePreset.teamsInQualifiers() - structurePreset.qualifiedPerGroupStage() * structurePreset.groupStageCount() |
|
|
|
|
groupStageCount = structurePreset.groupStageCount() |
|
|
|
|
teamsPerGroupStage = structurePreset.teamsPerGroupStage() |
|
|
|
|
qualifiedPerGroupStage = structurePreset.qualifiedPerGroupStage() |
|
|
|
|
groupStageAdditionalQualified = 0 |
|
|
|
|
buildWildcards = tournament.level.wildcardArePossible() |
|
|
|
|
if let selectedTournament { |
|
|
|
|
teamCount = selectedTournament.teamCount |
|
|
|
|
groupStageCount = selectedTournament.groupStageCount |
|
|
|
|
teamsPerGroupStage = selectedTournament.teamsPerGroupStage |
|
|
|
|
qualifiedPerGroupStage = selectedTournament.qualifiedPerGroupStage |
|
|
|
|
groupStageAdditionalQualified = selectedTournament.groupStageAdditionalQualified |
|
|
|
|
buildWildcards = tournament.level.wildcardArePossible() |
|
|
|
|
} else { |
|
|
|
|
teamCount = structurePreset.tableDimension() + structurePreset.teamsInQualifiers() - structurePreset.qualifiedPerGroupStage() * structurePreset.groupStageCount() |
|
|
|
|
groupStageCount = structurePreset.groupStageCount() |
|
|
|
|
teamsPerGroupStage = structurePreset.teamsPerGroupStage() |
|
|
|
|
qualifiedPerGroupStage = structurePreset.qualifiedPerGroupStage() |
|
|
|
|
groupStageAdditionalQualified = 0 |
|
|
|
|
buildWildcards = tournament.level.wildcardArePossible() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _verifyValueIntegrity() { |
|
|
|
|
@ -620,3 +730,41 @@ extension TableStructureView { |
|
|
|
|
// .environmentObject(DataStore.shared) |
|
|
|
|
// } |
|
|
|
|
//} |
|
|
|
|
|
|
|
|
|
func frenchUmpireOrder(for matches: [Int]) -> [Int] { |
|
|
|
|
guard matches.count > 1 else { return matches } |
|
|
|
|
|
|
|
|
|
// Base case |
|
|
|
|
if matches.count == 2 { |
|
|
|
|
return [matches[1], matches[0]] // bottom, top |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let n = matches.count |
|
|
|
|
let mid = n / 2 |
|
|
|
|
|
|
|
|
|
let topHalf = Array(matches[0..<mid]) |
|
|
|
|
let bottomHalf = Array(matches[mid..<n]) |
|
|
|
|
|
|
|
|
|
var order: [Int] = [] |
|
|
|
|
|
|
|
|
|
// Step 1: last match of round (bottom) |
|
|
|
|
order.append(bottomHalf.last!) |
|
|
|
|
|
|
|
|
|
// Step 2: first match of round (top) |
|
|
|
|
order.append(topHalf.first!) |
|
|
|
|
|
|
|
|
|
// Step 3 & 4: recursively apply on halves minus the ones we just used |
|
|
|
|
let topInner = Array(topHalf.dropFirst()) |
|
|
|
|
let bottomInner = Array(bottomHalf.dropLast()) |
|
|
|
|
|
|
|
|
|
let innerOrder = frenchUmpireOrder(for: bottomInner) + frenchUmpireOrder(for: topInner) |
|
|
|
|
order.append(contentsOf: innerOrder) |
|
|
|
|
|
|
|
|
|
return order |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Convenience wrapper to call by matchCount |
|
|
|
|
func frenchUmpireOrder(for matchCount: Int) -> [Int] { |
|
|
|
|
return frenchUmpireOrder(for: Array(0..<matchCount)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|