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.
 
 
PadelClub/PadelClub/Views/Tournament/Screen/TournamentRankView.swift

347 lines
14 KiB

//
// TournamentRankView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 30/04/2024.
//
import SwiftUI
import LeStorage
struct TournamentRankView: View {
@Environment(Tournament.self) var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
@Environment(\.editMode) private var editMode
@State private var rankings: [Int: [TeamRegistration]] = [:]
@State private var calculating = false
@State private var selectedTeam: TeamRegistration?
var tournamentStore: TournamentStore {
return self.tournament.tournamentStore
}
var isEditingTeam: Binding<Bool> {
Binding {
selectedTeam != nil
} set: { value in
}
}
var body: some View {
List {
@Bindable var tournament = tournament
let rankingsCalculated = tournament.selectedSortedTeams().anySatisfy({ $0.finalRanking != nil })
if editMode?.wrappedValue.isEditing == false {
Section {
let all = tournament.allMatches()
let runningMatches = tournament.runningMatches(all)
let matchesLeft = tournament.readyMatches(all)
MatchListView(section: "Matchs restant", matches: matchesLeft, hideWhenEmpty: false, isExpanded: false)
MatchListView(section: "Matchs en cours", matches: runningMatches, hideWhenEmpty: false, isExpanded: false)
Toggle(isOn: $tournament.hidePointsEarned) {
Text("Masquer les points gagnés")
}
.onChange(of: tournament.hidePointsEarned) {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
//affiche l'onglet sur le site, car sur le broadcast c'est dispo automatiquement de toute façon
Toggle(isOn: $tournament.publishRankings) {
Text("Publier sur Padel Club")
if let url = tournament.shareURL(.rankings) {
Link(destination: url) {
Text("Accéder à la page")
}
}
}
.onChange(of: tournament.publishRankings) {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
}
if (editMode?.wrappedValue.isEditing == true || rankingsCalculated == false) && calculating == false {
Section {
RowButtonView(rankingsCalculated ? "Re-calculer le classement" : "Calculer", role: .destructive) {
await _calculateRankings()
}
} footer: {
if rankingsCalculated {
Text("Vos éditions seront perdus.")
}
}
if rankingsCalculated {
Section {
RowButtonView("Supprimer le classement", role: .destructive) {
tournament.unsortedTeams().forEach { team in
team.finalRanking = nil
team.pointsEarned = nil
}
_save()
}
}
}
}
let teamsRanked = tournament.teamsRanked()
if calculating == false && rankingsCalculated && teamsRanked.isEmpty == false {
Section {
ForEach(teamsRanked) { team in
if let key = team.finalRanking {
TeamRankCellView(team: team, key: key)
}
}
}
}
}
.id(calculating)
.alert("Position", isPresented: isEditingTeam) {
if let selectedTeam {
@Bindable var team = selectedTeam
TextField("Position", value: $team.finalRanking, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
Button("Valider") {
selectedTeam.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: selectedTeam.finalRanking! - 1, count: tournament.teamCount)
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(instance: selectedTeam)
} catch {
Logger.error(error)
}
self.selectedTeam = nil
}
Button("Annuler", role: .cancel) {
self.selectedTeam = nil
}
}
}
.overlay(content: {
if calculating {
ProgressView()
}
})
.onAppear {
let rankingPublished = tournament.selectedSortedTeams().anySatisfy({ $0.finalRanking != nil })
if rankingPublished == false {
Task {
await _calculateRankings()
}
}
}
.navigationTitle("Classement")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
EditButton()
}
}
}
struct TeamRankCellView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(\.editMode) private var editMode
@Environment(Tournament.self) var tournament: Tournament
@State private var isEditingTeam: Bool = false
@Bindable var team: TeamRegistration
@State var key: Int
var body: some View {
VStack(spacing: 0) {
if editMode?.wrappedValue.isEditing == true {
if key > 1 {
FooterButtonView("monter") {
key -= 1
team.finalRanking = key
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount)
do {
try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
}
}
}
Button {
isEditingTeam = true
} label: {
HStack {
VStack(alignment: .trailing) {
VStack(alignment: .trailing, spacing: -8.0) {
ZStack(alignment: .trailing) {
Text(tournament.teamCount.formatted()).hidden()
Text(key.formatted())
}
.monospacedDigit()
.font(.largeTitle)
.fontWeight(.bold)
Text(key.ordinalFormattedSuffix()).font(.caption)
}
if let index = tournament.indexOf(team: team) {
ZStack {
HStack(spacing: 0.0) {
Text(tournament.teamCount.formatted(.number.sign(strategy: .always())))
.monospacedDigit()
Image(systemName: "arrowtriangle.down.fill")
.imageScale(.small)
}
.opacity(0)
let rankingDifference = index - (key - 1)
if rankingDifference > 0 {
HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
.monospacedDigit()
Image(systemName: "arrowtriangle.up.fill")
.imageScale(.small)
}
.foregroundColor(.green)
} else if rankingDifference < 0 {
HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
.monospacedDigit()
Image(systemName: "arrowtriangle.down.fill")
.imageScale(.small)
}
.foregroundColor(.logoRed)
} else {
Text("--")
}
}
}
}
Divider()
VStack(alignment: .leading) {
if let name = team.name {
Text(name).foregroundStyle(.secondary)
}
ForEach(team.players()) { player in
VStack(alignment: .leading, spacing: -4.0) {
Text(player.playerLabel()).bold()
HStack(alignment: .firstTextBaseline, spacing: 0.0) {
Text(player.rankLabel())
if let rank = player.getRank() {
Text(rank.ordinalFormattedSuffix())
.font(.caption)
}
}
}
}
}
Spacer()
if tournament.isAnimation() == false && key > 0 {
VStack(alignment: .trailing) {
HStack(alignment: .lastTextBaseline, spacing: 0.0) {
Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always())))
Text("pts").font(.caption)
}
}
}
}
.frame(maxWidth: .infinity)
}
.contentShape(Rectangle())
.buttonStyle(.plain)
if editMode?.wrappedValue.isEditing == true {
FooterButtonView("descendre") {
key += 1
team.finalRanking = key
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount)
do {
try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
}
}
}
.alert("Position", isPresented: $isEditingTeam) {
TextField("Position", value: $team.finalRanking, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
Button("Valider") {
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount)
do {
try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
isEditingTeam = false
}
Button("Annuler", role: .cancel) {
isEditingTeam = false
}
}
}
}
private func _calculateRankings() async {
await MainActor.run {
calculating = true
}
self.rankings.removeAll()
let finalRanks = await tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in
if let rankedTeamIds = finalRanks[rank] {
let teams: [TeamRegistration] = rankedTeamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) }
self.rankings[rank] = teams
}
}
await MainActor.run {
rankings.keys.sorted().forEach { rank in
if let rankedTeams = rankings[rank] {
rankedTeams.forEach { team in
team.finalRanking = rank
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount)
}
}
}
_save()
calculating = false
}
}
private func _save() {
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch {
Logger.error(error)
}
}
}
//#Preview {
// TournamentRankView()
//}