You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
8.5 KiB
215 lines
8.5 KiB
//
|
|
// BracketCallingView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by razmig on 15/10/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
import PadelClubData
|
|
|
|
struct BracketCallingView: View {
|
|
@EnvironmentObject var dataStore: DataStore
|
|
@Environment(Tournament.self) var tournament: Tournament
|
|
@State private var initialSeedRound: Int = 0
|
|
@State private var initialSeedCount: Int = 0
|
|
let tournamentRounds: [Round]
|
|
let teams: [TeamRegistration]
|
|
|
|
init(tournament: Tournament) {
|
|
let rounds = tournament.rounds()
|
|
self.tournamentRounds = rounds
|
|
self.teams = tournament.availableSeeds()
|
|
if tournament.initialSeedRound == 0, rounds.count > 0 {
|
|
let index = rounds.count - 1
|
|
_initialSeedRound = .init(wrappedValue: index)
|
|
_initialSeedCount = .init(wrappedValue: RoundRule.numberOfMatches(forRoundIndex: index))
|
|
} else if tournament.initialSeedRound < rounds.count {
|
|
_initialSeedRound = .init(wrappedValue: tournament.initialSeedRound)
|
|
_initialSeedCount = .init(wrappedValue: tournament.initialSeedCount)
|
|
} else if rounds.count > 0 {
|
|
let index = rounds.count - 1
|
|
_initialSeedRound = .init(wrappedValue: index)
|
|
_initialSeedCount = .init(wrappedValue: RoundRule.numberOfMatches(forRoundIndex: index))
|
|
}
|
|
}
|
|
|
|
var initialRound: Round {
|
|
tournamentRounds.first(where: { $0.index == initialSeedRound })!
|
|
}
|
|
|
|
func filteredRounds() -> [Round] {
|
|
tournamentRounds.filter({ $0.index >= initialSeedRound }).reversed()
|
|
}
|
|
|
|
func seedCount(forRoundIndex roundIndex: Int) -> Int {
|
|
if roundIndex < initialSeedRound { return 0 }
|
|
if roundIndex == initialSeedRound {
|
|
return initialSeedCount
|
|
}
|
|
|
|
let seedCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
|
|
let previousSeedCount = self.seedCount(forRoundIndex: roundIndex - 1)
|
|
|
|
let total = seedCount - previousSeedCount
|
|
if total < 0 { return 0 }
|
|
return total
|
|
}
|
|
|
|
func seeds(forRoundIndex roundIndex: Int) -> [TeamRegistration] {
|
|
let previousSeeds: Int = (initialSeedRound..<roundIndex).map { seedCount(forRoundIndex: $0) }.reduce(0, +)
|
|
|
|
|
|
if roundIndex == tournamentRounds.count - 1 {
|
|
return Array(teams.dropFirst(previousSeeds))
|
|
} else {
|
|
return Array(teams.dropFirst(previousSeeds).prefix(seedCount(forRoundIndex: roundIndex)))
|
|
}
|
|
}
|
|
|
|
|
|
var body: some View {
|
|
List {
|
|
let uncalledTeams = teams.filter({ $0.callDate == nil })
|
|
if uncalledTeams.isEmpty == false {
|
|
NavigationLink {
|
|
TeamsCallingView(teams: uncalledTeams)
|
|
.environment(tournament)
|
|
} label: {
|
|
LabeledContent("Équipe\(uncalledTeams.count.pluralSuffix) non contactée\(uncalledTeams.count.pluralSuffix)", value: uncalledTeams.count.formatted())
|
|
}
|
|
}
|
|
|
|
PlayersWithoutContactView(players: teams.flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank))
|
|
|
|
Section {
|
|
Picker(selection: $initialSeedRound) {
|
|
ForEach(tournamentRounds) {
|
|
Text($0.roundTitle()).tag($0.index)
|
|
}
|
|
} label: {
|
|
Text("Premier tour")
|
|
}
|
|
.onChange(of: initialSeedRound) {
|
|
initialSeedCount = RoundRule.numberOfMatches(forRoundIndex: initialSeedRound)
|
|
}
|
|
|
|
LabeledContent {
|
|
StepperView(count: $initialSeedCount, minimum: 0, maximum: RoundRule.numberOfMatches(forRoundIndex: initialSeedRound))
|
|
} label: {
|
|
Text("Têtes de série")
|
|
}
|
|
} footer: {
|
|
Text("Permet de convoquer par tour du tableau sans avoir tirer au sort les tétes de série. Vous pourrez ensuite confirmer leur horaire plus précis si le tour se joue sur plusieurs rotations. Les équipes ne peuvent pas être considéré comme convoqué au bon horaire en dehors de cet écran tant qu'elles n'ont pas été placé dans le tableau.")
|
|
}
|
|
|
|
ForEach(filteredRounds()) { round in
|
|
let seeds = seeds(forRoundIndex: round.index)
|
|
let startDate = ([round.startDate] + round.playedMatches().map { $0.startDate }).compacted().min()
|
|
let callSeeds = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0, expectedSummonDate: startDate) == false })
|
|
if seeds.isEmpty == false {
|
|
Section {
|
|
NavigationLink {
|
|
_roundView(round: round, seeds: seeds)
|
|
.environment(tournament)
|
|
} label: {
|
|
CallView.CallStatusView(count: callSeeds.count, total: seeds.count, startDate: startDate, title: "convoquées")
|
|
}
|
|
} header: {
|
|
Text(round.roundTitle())
|
|
} footer: {
|
|
if let startDate {
|
|
CallView(teams: seeds, callDate: startDate, matchFormat: round.matchFormat, roundLabel: round.roundTitle())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDisappear(perform: {
|
|
tournament.initialSeedCount = initialSeedCount
|
|
tournament.initialSeedRound = initialSeedRound
|
|
_save()
|
|
})
|
|
.headerProminence(.increased)
|
|
.navigationTitle("Pré-convocation")
|
|
}
|
|
|
|
private func _save() {
|
|
do {
|
|
try dataStore.tournaments.addOrUpdate(instance: tournament)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _roundView(round: Round, seeds: [TeamRegistration]) -> some View {
|
|
List {
|
|
|
|
let uncalledTeams = seeds.filter({ $0.callDate == nil })
|
|
if uncalledTeams.isEmpty == false {
|
|
NavigationLink {
|
|
TeamsCallingView(teams: uncalledTeams)
|
|
.environment(tournament)
|
|
} label: {
|
|
LabeledContent("Équipe\(uncalledTeams.count.pluralSuffix) non contactée\(uncalledTeams.count.pluralSuffix)", value: uncalledTeams.count.formatted())
|
|
}
|
|
}
|
|
|
|
let startDate = round.startDate ?? round.playedMatches().first?.startDate
|
|
let badCalled = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0, expectedSummonDate: startDate) })
|
|
|
|
if badCalled.isEmpty == false {
|
|
Section {
|
|
ForEach(badCalled) { team in
|
|
TeamCallView(team: team)
|
|
}
|
|
} header: {
|
|
HStack {
|
|
Text("Mauvais horaire")
|
|
Spacer()
|
|
Text(badCalled.count.formatted() + " équipe\(badCalled.count.pluralSuffix)")
|
|
}
|
|
} footer: {
|
|
if let startDate {
|
|
CallView(teams: badCalled, callDate: startDate, matchFormat: round.matchFormat, roundLabel: round.roundTitle())
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
ForEach(seeds) { team in
|
|
TeamCallView(team: team)
|
|
}
|
|
} header: {
|
|
HStack {
|
|
Text(round.roundTitle())
|
|
Spacer()
|
|
Text(seeds.count.formatted() + " équipe\(seeds.count.pluralSuffix)")
|
|
}
|
|
}
|
|
}
|
|
.overlay {
|
|
if seeds.isEmpty {
|
|
ContentUnavailableView {
|
|
Label("Aucune équipe dans ce tour", systemImage: "clock.badge.questionmark")
|
|
} description: {
|
|
Text("Padel Club n'a pas réussi à déterminer quelles équipes jouent ce tour.")
|
|
} actions: {
|
|
// RowButtonView("Horaire intelligent") {
|
|
// selectedScheduleDestination = nil
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
.headerProminence(.increased)
|
|
.navigationTitle(round.roundTitle())
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbarBackground(.visible, for: .navigationBar)
|
|
}
|
|
}
|
|
|
|
//#Preview {
|
|
// SeedsCallingView()
|
|
//}
|
|
|