Laurent 1 year ago
commit 5c98c367ae
  1. 6
      PadelClub.xcodeproj/project.pbxproj
  2. 19
      PadelClub/Data/Tournament.swift
  3. 66
      PadelClub/Views/Calling/CallView.swift
  4. 164
      PadelClub/Views/Cashier/CashierDetailView.swift
  5. 46
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  6. 304
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  7. 136
      PadelClub/Views/Tournament/TournamentBuildView.swift
  8. 8
      PadelClub/Views/Tournament/TournamentView.swift

@ -1859,7 +1859,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 50; CURRENT_PROJECT_VERSION = 53;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1882,7 +1882,7 @@
); );
MARKETING_VERSION = 0.1; MARKETING_VERSION = 0.1;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=5 -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=50"; OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -1897,7 +1897,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 50; CURRENT_PROJECT_VERSION = 53;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -867,7 +867,15 @@ class Tournament : ModelObject, Storable {
func selectedPlayers() -> [PlayerRegistration] { func selectedPlayers() -> [PlayerRegistration] {
return self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) return self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
} }
func paidSelectedPlayers(type: PlayerRegistration.PlayerPaymentType) -> Double? {
if let entryFee {
return Double(self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.filter { $0.paymentType == type }.count) * entryFee
} else {
return nil
}
}
func players() -> [PlayerRegistration] { func players() -> [PlayerRegistration] {
return self.unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) return self.unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
} }
@ -1065,8 +1073,13 @@ class Tournament : ModelObject, Storable {
let _limit = limit ?? courtCount let _limit = limit ?? courtCount
return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit))
} }
func finalRanking() -> [Int: [String]] { func teamsRanked() -> [TeamRegistration] {
let selected = selectedSortedTeams().filter({ $0.finalRanking != nil })
return selected.sorted(by: \.finalRanking!, order: .ascending)
}
func finalRanking() async -> [Int: [String]] {
var teams: [Int: [String]] = [:] var teams: [Int: [String]] = [:]
var ids: Set<String> = Set<String>() var ids: Set<String> = Set<String>()
let rounds = rounds() let rounds = rounds()

@ -85,16 +85,16 @@ struct CallView: View {
} }
} }
var finalMessage: String { func finalMessage(reSummon: Bool) -> String {
ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat, reSummon: reSummon) ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat, reSummon: reSummon)
} }
var reSummon: Bool { var reSummon: Bool {
teams.allSatisfy({ $0.called() }) teams.allSatisfy({ $0.called() })
} }
var body: some View { var body: some View {
let callWord = reSummon ? "Reconvoquer" : "Convoquer" let callWord : String = (reSummon ? "Reconvoquer" : "Convoquer")
HStack { HStack {
if teams.count == 1 { if teams.count == 1 {
if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame { if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame {
@ -105,23 +105,10 @@ struct CallView: View {
} else { } else {
Text(callWord + " ces \(teams.count) paires par") Text(callWord + " ces \(teams.count) paires par")
} }
Button {
self._payTournamentAndExecute { _summonMenu(byMessage: true)
self._contactByMessage()
}
} label: {
Text("sms")
.underline()
}
Text("ou") Text("ou")
Button { _summonMenu(byMessage: false)
self._payTournamentAndExecute {
self._contactByMail()
}
} label: {
Text("mail")
.underline()
}
} }
.font(.subheadline) .font(.subheadline)
.buttonStyle(.borderless) .buttonStyle(.borderless)
@ -182,6 +169,39 @@ struct CallView: View {
}) })
} }
@ViewBuilder
private func _summonMenu(byMessage: Bool) -> some View {
if reSummon {
Menu {
Button("Convoquer") {
_summon(byMessage: byMessage, reSummon: false)
}
Button("Re-convoquer") {
_summon(byMessage: byMessage, reSummon: true)
}
} label: {
Text(byMessage ? "sms" : "mail")
.underline()
}
} else {
Button(byMessage ? "sms" : "mail") {
_summon(byMessage: byMessage, reSummon: false)
}
}
}
private func _summon(byMessage: Bool, reSummon: Bool) {
self._payTournamentAndExecute {
if byMessage {
self._contactByMessage(reSummon: reSummon)
} else {
self._contactByMail(reSummon: reSummon)
}
}
}
fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { fileprivate func _payTournamentAndExecute(_ handler: () -> ()) {
do { do {
try tournament.payIfNecessary() try tournament.payIfNecessary()
@ -191,12 +211,12 @@ struct CallView: View {
} }
} }
fileprivate func _contactByMessage() { fileprivate func _contactByMessage(reSummon: Bool) {
contactType = .message(date: callDate, recipients: teams.flatMap { $0.getPhoneNumbers() }, body: finalMessage, tournamentBuild: nil) contactType = .message(date: callDate, recipients: teams.flatMap { $0.getPhoneNumbers() }, body: finalMessage(reSummon: reSummon), tournamentBuild: nil)
} }
fileprivate func _contactByMail() { fileprivate func _contactByMail(reSummon: Bool) {
contactType = .mail(date: callDate, recipients: tournament.umpireMail(), bccRecipients: teams.flatMap { $0.getMail() }, body: finalMessage, subject: tournament.tournamentTitle(), tournamentBuild: nil) contactType = .mail(date: callDate, recipients: tournament.umpireMail(), bccRecipients: teams.flatMap { $0.getMail() }, body: finalMessage(reSummon: reSummon), subject: tournament.tournamentTitle(), tournamentBuild: nil)
} }
} }

@ -9,6 +9,8 @@ import SwiftUI
struct CashierDetailView: View { struct CashierDetailView: View {
var tournaments : [Tournament] var tournaments : [Tournament]
@State private var earnings: Double? = nil
@State private var paidCompletion: Double? = nil
init(tournaments: [Tournament]) { init(tournaments: [Tournament]) {
self.tournaments = tournaments self.tournaments = tournaments
@ -20,58 +22,154 @@ struct CashierDetailView: View {
var body: some View { var body: some View {
List { List {
ForEach(tournaments) { tournament in if tournaments.count > 1 {
Section { Section {
LabeledContent { LabeledContent {
Text(tournament.earnings().formatted(.currency(code: "EUR").precision(.fractionLength(0)))) if let earnings {
Text(earnings.formatted(.currency(code: "EUR").precision(.fractionLength(0))))
} else {
ProgressView()
}
} label: { } label: {
Text("Encaissement") Text("Encaissement")
Text(tournament.paidCompletion().formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) if let paidCompletion {
Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
} }
_tournamentCashierDetailView(tournament) _tournamentsCashierDetailView(tournaments)
} header: { } header: {
if tournaments.count > 1 { Text("Bilan")
Text(tournament.tournamentTitle())
}
} }
} }
ForEach(tournaments) { tournament in
CashierSectionView(tournament: tournament, showTournamentTitle: tournaments.count > 1)
}
} }
.headerProminence(.increased) .headerProminence(.increased)
.onAppear {
Task {
if earnings == nil {
_getEarnings()
}
if paidCompletion == nil {
_getPaidCompletion()
}
}
}
} }
private func _tournamentCashierDetailView(_ tournament: Tournament) -> some View { private func _getEarnings() {
earnings = tournaments.map { $0.earnings() }.reduce(0,+)
}
private func _getPaidCompletion() {
let selectedPlayers = tournaments.flatMap { $0.selectedPlayers() }
if selectedPlayers.isEmpty { paidCompletion = 0 }
paidCompletion = Double(selectedPlayers.filter { $0.hasPaid() }.count) / Double(selectedPlayers.count)
}
private func _tournamentsCashierDetailView(_ tournaments: [Tournament]) -> some View {
DisclosureGroup { DisclosureGroup {
ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in
let count = tournament.selectedPlayers().filter({ $0.paymentType == type }).count PaymentTypeCashierRowView(tournaments: tournaments, type: type)
if count > 0 {
LabeledContent {
if let entryFee = tournament.entryFee {
let sum = Double(count) * entryFee
Text(sum.formatted(.currency(code: "EUR")))
}
} label: {
Text(type.localizedLabel())
Text(count.formatted())
}
}
} }
} label: { } label: {
Text("Voir le détail") Text("Voir le détail")
} }
}
struct CashierSectionView: View {
let tournament: Tournament
let showTournamentTitle: Bool
@State private var earnings: Double? = nil
@State private var paidCompletion: Double? = nil
var body: some View {
Section {
LabeledContent {
if let earnings {
Text(earnings.formatted(.currency(code: "EUR").precision(.fractionLength(0))))
} else {
ProgressView()
}
} label: {
Text("Encaissement")
if let paidCompletion {
Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
}
CashierDetailDisclosureView(tournament: tournament)
} header: {
if showTournamentTitle {
Text(tournament.tournamentTitle())
}
}
.onAppear {
Task {
if earnings == nil {
earnings = tournament.earnings()
}
if paidCompletion == nil {
paidCompletion = tournament.paidCompletion()
}
}
}
}
}
// struct PaymentTypeCashierRowView: View {
// Section { let tournaments: [Tournament]
// ForEach(tournaments) { tournament in let type: PlayerRegistration.PlayerPaymentType
// } @State private var value: Double?
//// HStack {
//// Text("Total")
//// Spacer() var body: some View {
//// Text(event.earnings.formatted(.currency(code: "EUR").precision(.fractionLength(0)))) LabeledContent {
//// Text(event.paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) if let value {
//// } Text(value.formatted(.currency(code: "EUR")))
// } header: { } else {
// Text("Encaissement") ProgressView()
// } }
} label: {
Text(type.localizedLabel())
}
.onAppear {
Task {
if value == nil {
value = tournaments.compactMap({ $0.paidSelectedPlayers(type: type) }).reduce(0,+)
}
}
}
}
}
struct CashierDetailDisclosureView: View {
let tournament: Tournament
var body: some View {
DisclosureGroup {
let selectedPlayers = tournament.selectedPlayers()
ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in
let count = selectedPlayers.filter({ $0.paymentType == type }).count
if count > 0 {
LabeledContent {
if let entryFee = tournament.entryFee {
let sum = Double(count) * entryFee
Text(sum.formatted(.currency(code: "EUR")))
}
} label: {
Text(type.localizedLabel())
Text(count.formatted())
}
}
}
} label: {
Text("Voir le détail")
}
}
} }
} }

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import LeStorage
struct TableStructureView: View { struct TableStructureView: View {
@Environment(Tournament.self) private var tournament: Tournament @Environment(Tournament.self) private var tournament: Tournament
@ -133,6 +134,24 @@ struct TableStructureView: View {
Text("Équipes en tableau final") Text("Équipes en tableau final")
} }
} }
Section {
RowButtonView("Sauver sans reconstuire l'existant") {
_saveWithoutRebuild()
}
}
Section {
RowButtonView("Recontruire les poules", role:.destructive) {
_save(rebuildEverything: false)
}
}
Section {
RowButtonView("Tout refaire", role: .destructive) {
_save(rebuildEverything: true)
}
}
} }
.focused($stepperFieldIsFocused) .focused($stepperFieldIsFocused)
.onChange(of: stepperFieldIsFocused) { .onChange(of: stepperFieldIsFocused) {
@ -204,17 +223,17 @@ struct TableStructureView: View {
} }
} }
.confirmationDialog("Refaire la structure", isPresented: $presentRefreshStructureWarning, actions: { .confirmationDialog("Refaire la structure", isPresented: $presentRefreshStructureWarning, actions: {
Button("Sauver sans reconstuire l'existant") {
_saveWithoutRebuild()
}
if requirements.allSatisfy({ $0 == .groupStage }) { Button("Recontruire les poules") {
Button("Refaire les poules") { _save(rebuildEverything: false)
_save(rebuildEverything: false)
}
} }
Button("Tout refaire", role: .destructive) { Button("Tout refaire", role: .destructive) {
_save(rebuildEverything: true) _save(rebuildEverything: true)
} }
}, message: { }, message: {
ForEach(Array(requirements)) { requirement in ForEach(Array(requirements)) { requirement in
Text(requirement.rebuildingRequirementMessage) Text(requirement.rebuildingRequirementMessage)
@ -226,7 +245,22 @@ struct TableStructureView: View {
.navigationTitle("Structure") .navigationTitle("Structure")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
private func _saveWithoutRebuild() {
tournament.teamCount = teamCount
tournament.groupStageCount = groupStageCount
tournament.teamsPerGroupStage = teamsPerGroupStage
tournament.qualifiedPerGroupStage = qualifiedPerGroupStage
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
dismiss()
}
private func _save(rebuildEverything: Bool = false) { private func _save(rebuildEverything: Bool = false) {
_verifyValueIntegrity() _verifyValueIntegrity()

@ -13,13 +13,22 @@ struct TournamentRankView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State private var rankings: [Int: [TeamRegistration]] = [:] @State private var rankings: [Int: [TeamRegistration]] = [:]
@State private var calculating = false
@State private var selectedTeam: TeamRegistration?
var isEditingTeam: Binding<Bool> {
Binding {
selectedTeam != nil
} set: { value in
}
}
var body: some View { var body: some View {
List { List {
@Bindable var tournament = tournament @Bindable var tournament = tournament
let matchs = tournament.runningMatches(tournament.allMatches())
let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil })
Section { Section {
let matchs = tournament.runningMatches(tournament.allMatches())
let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil })
LabeledContent { LabeledContent {
Text(matchs.count.formatted()) Text(matchs.count.formatted())
} label: { } label: {
@ -29,8 +38,10 @@ struct TournamentRankView: View {
LabeledContent { LabeledContent {
if rankingPublished { if rankingPublished {
Image(systemName: "checkmark") Image(systemName: "checkmark")
.foregroundStyle(.green)
} else { } else {
Image(systemName: "xmark") Image(systemName: "xmark")
.foregroundStyle(.logoRed)
} }
} label: { } label: {
Text("Classement publié") Text("Classement publié")
@ -47,116 +58,227 @@ struct TournamentRankView: View {
} }
} }
RowButtonView(rankingPublished ? "Re-publier le classement" : "Publier le classement", role: .destructive) { if rankingPublished == false {
rankings.keys.sorted().forEach { rank in RowButtonView("Publier le classement", role: .destructive) {
if let rankedTeams = rankings[rank] { _publishRankings()
rankedTeams.forEach { team in }
team.finalRanking = rank } else {
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount) RowButtonView("Re-publier le classement", role: .destructive) {
} _publishRankings()
}
} }
_save()
} }
} footer: { }
FooterButtonView("masquer le classement", role: .destructive) {
tournament.unsortedTeams().forEach { team in if rankingPublished {
team.finalRanking = nil Section {
team.pointsEarned = nil RowButtonView("Supprimer le classement", role: .destructive) {
tournament.unsortedTeams().forEach { team in
team.finalRanking = nil
team.pointsEarned = nil
}
_save()
} }
_save() } footer: {
Text(.init("Masque également le classement sur le site [Padel Club](\(URLs.main.rawValue))"))
} }
} }
let keys = rankings.keys.sorted() if rankingPublished {
ForEach(keys, id: \.self) { key in Section {
if let rankedTeams = rankings[key] { ForEach(tournament.teamsRanked()) { team in
ForEach(rankedTeams) { team in let key = team.finalRanking ?? 0
HStack { Button {
VStack(alignment: .trailing) { selectedTeam = team
VStack(alignment: .trailing, spacing: -8.0) { } label: {
ZStack(alignment: .trailing) { TeamRankCellView(team: team, key: key)
Text(tournament.teamCount.formatted()).hidden() .frame(maxWidth: .infinity)
Text(key.formatted()) }
} .contentShape(Rectangle())
.buttonStyle(.plain)
}
} footer: {
Text("Vous pouvez appuyer sur une ligne pour éditer manuellement le classement calculé par Padel Club.")
}
} else {
let keys = rankings.keys.sorted()
ForEach(keys, id: \.self) { key in
if let rankedTeams = rankings[key] {
ForEach(rankedTeams) { team in
TeamRankCellView(team: team, key: key)
}
}
}
}
}
.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 dataStore.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().allSatisfy({ $0.finalRanking != nil })
if rankingPublished == false {
calculating = true
Task {
await _calculateRankings()
calculating = false
}
}
}
.navigationTitle("Classement")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if let url = tournament.shareURL(.rankings) {
_actionForURL(url)
}
}
}
}
struct TeamRankCellView: View {
@Environment(Tournament.self) var tournament: Tournament
let team: TeamRegistration
let key: Int
var body: some View {
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) {
let rankingDifference = index - (key - 1)
if rankingDifference > 0 {
HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
.monospacedDigit() .monospacedDigit()
.font(.largeTitle) Image(systemName: "arrowtriangle.up.fill")
.fontWeight(.bold) .imageScale(.small)
Text(key.ordinalFormattedSuffix()).font(.caption)
}
if let index = tournament.indexOf(team: team) {
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(.red)
} else {
Text("--")
}
}
} }
.foregroundColor(.green)
} else if rankingDifference < 0 {
Divider() HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
VStack(alignment: .leading) { .monospacedDigit()
if let name = team.name { Image(systemName: "arrowtriangle.down.fill")
Text(name).foregroundStyle(.secondary) .imageScale(.small)
}
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)
}
}
}
}
} }
.foregroundColor(.red)
if tournament.isAnimation() == false { } else {
Spacer() Text("--")
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)
}
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)
} }
} }
} }
} }
} }
if tournament.isAnimation() == false && key > 0 {
Spacer()
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)
}
}
}
} }
} }
.onAppear { }
let finalRanks = tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in private func _publishRankings() {
if let rankedTeamIds = finalRanks[rank] { rankings.keys.sorted().forEach { rank in
rankings[rank] = rankedTeamIds.compactMap { Store.main.findById($0) } 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)
} }
} }
} }
.navigationTitle("Classement") _save()
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
} }
private func _calculateRankings() async {
let finalRanks = await tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in
if let rankedTeamIds = finalRanks[rank] {
rankings[rank] = rankedTeamIds.compactMap { Store.main.findById($0) }
}
}
}
@ViewBuilder
private func _actionForURL(_ url: URL, removeSource: Bool = false) -> some View {
Menu {
Button {
UIApplication.shared.open(url)
} label: {
Label("Voir", systemImage: "safari")
}
ShareLink(item: url) {
Label("Partager le lien", systemImage: "link")
}
} label: {
Image(systemName: "square.and.arrow.up")
}
.frame(maxWidth: .infinity)
.buttonStyle(.borderless)
}
private func _save() { private func _save() {
do { do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())

@ -18,85 +18,38 @@ struct TournamentBuildView: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
let state = tournament.state() let state = tournament.state()
if tournament.hasEnded() { Section {
Section { if tournament.hasEnded() {
NavigationLink(value: Screen.rankings) { NavigationLink(value: Screen.rankings) {
Text("Classement final des équipes") Text("Classement final des équipes")
} }
} }
}
Section {
if tournament.groupStageCount > 0 {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
if let groupStageStatus {
Text(groupStageStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: {
Text("Poules")
if tournament.shouldVerifyGroupStage {
Text("Vérifier les poules").foregroundStyle(.logoRed)
}
}
}
.task {
groupStageStatus = await tournament.groupStageStatus()
}
}
if tournament.rounds().isEmpty == false {
NavigationLink(value: Screen.round) {
LabeledContent {
if let bracketStatus {
Text(bracketStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: {
Text("Tableau")
if tournament.shouldVerifyBracket {
Text("Vérifier la tableau").foregroundStyle(.logoRed)
}
}
}
.task {
bracketStatus = await tournament.bracketStatus()
}
}
}
Section {
if state == .running || state == .finished { if state == .running || state == .finished {
TournamentInscriptionView(tournament: tournament)
TournamentBroadcastRowView(tournament: tournament) TournamentBroadcastRowView(tournament: tournament)
} }
if state == .running || state == .finished { NavigationLink(value: Screen.cashier) {
NavigationLink(value: Screen.cashier) { let tournamentStatus = cashierStatus
let tournamentStatus = cashierStatus LabeledContent {
LabeledContent { if let tournamentStatus {
if let tournamentStatus { Text(tournamentStatus.completion)
Text(tournamentStatus.completion) } else {
} else { ProgressView()
ProgressView() }
} } label: {
} label: { Text("Encaissement")
Text("Encaissement") if let tournamentStatus {
if let tournamentStatus { Text(tournamentStatus.label).lineLimit(1)
Text(tournamentStatus.label).lineLimit(1) } else {
} else { Text(" ")
Text(" ")
}
} }
} }
.task { }
cashierStatus = await tournament.cashierStatus() .task {
} cashierStatus = await tournament.cashierStatus()
} }
if state != .finished { if state != .finished {
@ -142,11 +95,50 @@ struct TournamentBuildView: View {
callStatus = await tournament.callStatus() callStatus = await tournament.callStatus()
} }
} }
}
Section {
if tournament.groupStageCount > 0 {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
if let groupStageStatus {
Text(groupStageStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: {
Text("Poules")
if tournament.shouldVerifyGroupStage {
Text("Vérifier les poules").foregroundStyle(.logoRed)
}
}
}
.task {
groupStageStatus = await tournament.groupStageStatus()
}
}
if state == .running || state == .finished { if tournament.rounds().isEmpty == false {
TournamentInscriptionView(tournament: tournament) NavigationLink(value: Screen.round) {
LabeledContent {
if let bracketStatus {
Text(bracketStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: {
Text("Tableau")
if tournament.shouldVerifyBracket {
Text("Vérifier la tableau").foregroundStyle(.logoRed)
}
}
}
.task {
bracketStatus = await tournament.bracketStatus()
}
} }
} }
} }
} }

@ -67,8 +67,8 @@ struct TournamentView: View {
TournamentInitView(tournament: tournament) TournamentInitView(tournament: tournament)
TournamentBuildView(tournament: tournament) TournamentBuildView(tournament: tournament)
case .running: case .running:
TournamentRunningView(tournament: tournament)
TournamentBuildView(tournament: tournament) TournamentBuildView(tournament: tournament)
TournamentRunningView(tournament: tournament)
case .finished: case .finished:
TournamentBuildView(tournament: tournament) TournamentBuildView(tournament: tournament)
TournamentRunningView(tournament: tournament) TournamentRunningView(tournament: tournament)
@ -121,6 +121,12 @@ struct TournamentView: View {
} label: { } label: {
} }
Divider()
NavigationLink(value: Screen.event) {
Text("Gestion de l'événement")
}
} }
} }

Loading…
Cancel
Save