Laurent 1 year ago
commit a85da54f96
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 7
      PadelClub/Data/Coredata/Persistence.swift
  3. 6
      PadelClub/Data/GroupStage.swift
  4. 13
      PadelClub/Data/Match.swift
  5. 4
      PadelClub/Data/PlayerRegistration.swift
  6. 10
      PadelClub/Data/Round.swift
  7. 52
      PadelClub/Data/Tournament.swift
  8. 12
      PadelClub/Extensions/String+Extensions.swift
  9. 1
      PadelClub/PadelClubApp.swift
  10. 14
      PadelClub/Utils/ContactManager.swift
  11. 4
      PadelClub/Utils/FileImportManager.swift
  12. 50
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  13. 30
      PadelClub/Views/Calling/CallSettingsView.swift
  14. 8
      PadelClub/Views/Calling/CallView.swift
  15. 10
      PadelClub/Views/Calling/SendToAllView.swift
  16. 2
      PadelClub/Views/Cashier/CashierView.swift
  17. 5
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  18. 134
      PadelClub/Views/Match/MatchDetailView.swift
  19. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  20. 2
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  21. 39
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  22. 2
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  23. 43
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  24. 58
      PadelClub/Views/Planning/SchedulerView.swift
  25. 13
      PadelClub/Views/Player/Components/PlayerPopoverView.swift
  26. 3
      PadelClub/Views/Player/PlayerDetailView.swift
  27. 6
      PadelClub/Views/Round/RoundSettingsView.swift
  28. 2
      PadelClub/Views/Shared/LearnMoreSheetView.swift
  29. 50
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  30. 44
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  31. 2
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift
  32. 2
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  33. 2
      PadelClub/Views/Tournament/TournamentInscriptionView.swift
  34. 2
      PadelClub/Views/Tournament/TournamentView.swift
  35. 5
      PadelClubTests/ServerDataTests.swift

@ -1919,7 +1919,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 21;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1957,7 +1957,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 21;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -155,15 +155,16 @@ class PersistenceController: NSObject {
importedPlayer.country?.replace(characters: replacementsCharacters)
}
importedPlayer.tournamentCount = Int64(data.tournamentCount ?? 0)
importedPlayer.lastName = data.lastName
importedPlayer.lastName = data.lastName.trimmed.uppercased()
if fixApril2024 {
importedPlayer.lastName?.replace(characters: replacementsCharacters)
}
importedPlayer.firstName = data.firstName
importedPlayer.firstName = data.firstName.trimmed.capitalized
if fixApril2024 {
importedPlayer.firstName?.replace(characters: replacementsCharacters)
}
importedPlayer.fullName = data.firstName + " " + data.lastName
importedPlayer.fullName = data.firstName.trimmed.capitalized + " " + data.lastName.trimmed.uppercased()
if fixApril2024 {
importedPlayer.fullName?.replace(characters: replacementsCharacters)
}

@ -304,7 +304,11 @@ class GroupStage: ModelObject, Storable {
playedMatches.forEach { match in
match.matchFormat = matchFormat
}
try? DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches)
do {
try DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches)
} catch {
Logger.error(error)
}
}
override func deleteDependencies() throws {

@ -168,6 +168,19 @@ class Match: ModelObject, Storable {
servingTeamId = nil
}
func resetScores() {
if hasEnded() == false {
teamScores.forEach({ $0.score = nil })
do {
try DataStore.shared.teamScores.addOrUpdate(contentOfs: teamScores)
} catch {
Logger.error(error)
}
} else {
updateTeamScores()
}
}
func teamWillBeWalkOut(_ team: TeamRegistration) {
resetMatch()
let previousScores = teamScores.filter({ $0.luckyLoser != nil })

@ -60,8 +60,8 @@ class PlayerRegistration: ModelObject, Storable {
internal init(importedPlayer: ImportedPlayer) {
self.teamRegistration = ""
self.firstName = importedPlayer.firstName ?? ""
self.lastName = importedPlayer.lastName ?? ""
self.firstName = (importedPlayer.firstName ?? "").trimmed.capitalized
self.lastName = (importedPlayer.lastName ?? "").trimmed.uppercased()
self.licenceId = importedPlayer.license ?? nil
self.rank = Int(importedPlayer.rank)
self.sex = importedPlayer.male ? .male : .female

@ -369,9 +369,9 @@ class Round: ModelObject, Storable {
func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if parent != nil {
return seedInterval()?.localizedLabel(displayStyle) ?? "Pas trouvé"
return seedInterval()?.localizedLabel(displayStyle) ?? "Round pas trouvé"
}
return RoundRule.roundName(fromRoundIndex: index)
return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle)
}
func updateTournamentState() {
@ -467,7 +467,11 @@ class Round: ModelObject, Storable {
playedMatches.forEach { match in
match.matchFormat = updatedMatchFormat
}
try? DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches)
do {
try DataStore.shared.matches.addOrUpdate(contentOfs: playedMatches)
} catch {
Logger.error(error)
}
}
override func deleteDependencies() throws {

@ -48,8 +48,6 @@ class Tournament : ModelObject, Storable {
var publishSummons: Bool = false
var publishGroupStages: Bool = false
var publishBrackets: Bool = false
//local
var shouldVerifyGroupStage: Bool = false
var shouldVerifyBracket: Bool = false
@ -94,9 +92,11 @@ class Tournament : ModelObject, Storable {
case _publishSummons = "publishSummons"
case _publishGroupStages = "publishGroupStages"
case _publishBrackets = "publishBrackets"
case _shouldVerifyGroupStage = "shouldVerifyGroupStage"
case _shouldVerifyBracket = "shouldVerifyBracket"
}
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) {
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, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false) {
self.event = event
self.name = name
self.startDate = startDate
@ -128,6 +128,8 @@ class Tournament : ModelObject, Storable {
self.publishSummons = publishSummons
self.publishBrackets = publishBrackets
self.publishGroupStages = publishGroupStages
self.shouldVerifyBracket = shouldVerifyBracket
self.shouldVerifyGroupStage = shouldVerifyGroupStage
}
required init(from decoder: Decoder) throws {
@ -166,7 +168,8 @@ class Tournament : ModelObject, Storable {
publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false
publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false
publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false
shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false
shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false
}
fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter()
@ -276,6 +279,8 @@ class Tournament : ModelObject, Storable {
try container.encode(publishSummons, forKey: ._publishSummons)
try container.encode(publishBrackets, forKey: ._publishBrackets)
try container.encode(publishGroupStages, forKey: ._publishGroupStages)
try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket)
try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage)
}
fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws {
@ -338,6 +343,14 @@ class Tournament : ModelObject, Storable {
case finished
}
func eventLabel() -> String {
if let event = eventObject(), let name = event.name {
return name
} else {
return ""
}
}
func publishedTeamsDate() -> Date {
startDate
}
@ -750,6 +763,14 @@ class Tournament : ModelObject, Storable {
Store.main.filter { $0.tournament == self.id }
}
func unsortedTeamsWithoutWO() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id && $0.walkOut == false }
}
func walkoutTeams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id && $0.walkOut == true }
}
func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] {
var duplicates = [PlayerRegistration]()
Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
@ -847,12 +868,20 @@ class Tournament : ModelObject, Storable {
func importTeams(_ teams: [FileImportManager.TeamHolder]) {
var teamsToImport = [TeamRegistration]()
let players = players().filter { $0.licenceId != nil }
teams.forEach { team in
if let previousTeam = team.previousTeam {
previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory)
teamsToImport.append(previousTeam)
} else {
let newTeam = addTeam(team.players, registrationDate: team.registrationDate)
var registrationDate = team.registrationDate
if let previousPlayer = players.first(where: { player in
let ids = team.players.compactMap({ $0.licenceId })
return ids.contains(player.licenceId!)
}), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate {
registrationDate = previousTeamRegistrationDate
}
let newTeam = addTeam(team.players, registrationDate: registrationDate)
teamsToImport.append(newTeam)
}
}
@ -1017,7 +1046,7 @@ class Tournament : ModelObject, Storable {
}
func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String {
[tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ")
[tournamentLevel.localizedLabel(.wide) + " " + tournamentCategory.localizedLabel(.wide), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ")
}
func subtitle(_ displayStyle: DisplayStyle = .wide) -> String {
@ -1128,6 +1157,15 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel)
}
func confirmedSummonStatus() -> TournamentStatus {
let selectedSortedTeams = selectedSortedTeams()
let called = selectedSortedTeams.filter { $0.confirmationDate != nil }
let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées"
let completion = (Double(called.count) / Double(selectedSortedTeams.count))
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
return TournamentStatus(label: label, completion: completionLabel)
}
func bracketStatus() -> String {
let availableSeeds = availableSeeds()
if availableSeeds.isEmpty == false {
@ -1711,7 +1749,7 @@ extension Tournament: FederalTournamentHolder {
extension Tournament: TournamentBuildHolder {
func buildHolderTitle() -> String {
tournamentTitle()
tournamentTitle(.short)
}
var category: TournamentCategory {

@ -174,3 +174,15 @@ extension StringProtocol {
extension LosslessStringConvertible {
var string: String { .init(self) }
}
extension String {
func createTxtFile(_ withName: String = "temp") -> URL {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(withName)
.appendingPathExtension("txt")
let string = self
try? FileManager.default.removeItem(at: url)
try? string.write(to: url, atomically: true, encoding: .utf8)
return url
}
}

@ -22,6 +22,7 @@ struct PadelClubApp: App {
.environment(navigationViewModel)
.accentColor(.master)
.onAppear {
networkMonitor.checkConnection()
self._onAppear()
}
.task {

@ -31,7 +31,7 @@ enum ContactType: Identifiable {
extension ContactType {
static let defaultCustomMessage = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire."
static let defaultAvailablePaymentMethods = "Règlement possible par chèque ou espèce."
static let defaultAvailablePaymentMethods = "Règlement possible par chèque ou espèces."
static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String {
let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
@ -41,7 +41,7 @@ extension ContactType {
let date = startDate ?? tournament?.startDate ?? Date()
if let tournament {
text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle())
text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.short))
text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage)
}
@ -56,7 +56,7 @@ extension ContactType {
return text
}
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String {
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, reSummon: Bool = false) -> String {
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
@ -77,13 +77,15 @@ extension ContactType {
}
var computedMessage: String {
[entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n")
[entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n\n")
}
let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes"
if let tournament {
return "Bonjour,\n\nVous êtes \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle()) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)"
return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.short)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)"
} else {
return "Bonjour,\n\nVous êtes \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)"
return "Bonjour,\n\n\(intro) \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)"
}
}
}

@ -27,8 +27,8 @@ class FileImportManager {
lastName.replace(characters: replacementsCharacters)
var firstName = federalPlayer.firstName
firstName.replace(characters: replacementsCharacters)
importedPlayer.lastName = lastName
importedPlayer.firstName = firstName
importedPlayer.lastName = lastName.trimmed.uppercased()
importedPlayer.firstName = firstName.trimmed.capitalized
}
}
playersLeft.removeAll(where: { $0.lastName.isEmpty == false })

@ -24,6 +24,8 @@ struct CallMessageCustomizationView: View {
@State private var customCallMessageSignature: String = ""
@State private var summonsAvailablePaymentMethods: String = ""
var columns: [GridItem] = Array(repeating: .init(.flexible()), count: 3)
init(tournament: Tournament) {
self.tournament = tournament
_customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? (DataStore.shared.user.summonsUseFullCustomMessage ? "" : ContactType.defaultCustomMessage))
@ -46,7 +48,7 @@ struct CallMessageCustomizationView: View {
var finalMessage: String? {
let localizedCalled = "convoqué" + (tournament.tournamentCategory == .women ? "e" : "") + "s"
return "Bonjour,\n\nVous êtes \(localizedCalled) pour jouer en \(RoundRule.roundName(fromRoundIndex: 2).lowercased()) du \(tournament.tournamentTitle()) au \(clubName) le \(tournament.startDate.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(tournament.startDate.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(customCallMessageSignature)"
return "Bonjour,\n\nVous êtes \(localizedCalled) pour jouer en \(RoundRule.roundName(fromRoundIndex: 2).lowercased()) du \(tournament.tournamentTitle(.short)) au \(clubName) le \(tournament.startDate.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(tournament.startDate.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(customCallMessageSignature)"
}
var body: some View {
@ -73,6 +75,16 @@ struct CallMessageCustomizationView: View {
} footer: {
HStack {
Spacer()
FooterButtonView("effacer") {
customCallMessageSignature = ""
_save()
}
Divider()
FooterButtonView("défaut") {
customCallMessageSignature = DataStore.shared.user.defaultSignature()
_save()
}
Divider()
FooterButtonView("éditer") {
focusedField = .signature
}
@ -91,9 +103,6 @@ struct CallMessageCustomizationView: View {
Spacer()
Button {
focusedField = nil
user.summonsMessageBody = customCallMessageBody
user.summonsMessageSignature = customCallMessageSignature
user.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods
_save()
} label: {
Text("Valider")
@ -117,6 +126,9 @@ struct CallMessageCustomizationView: View {
}
private func _save() {
self.dataStore.user.summonsMessageBody = customCallMessageBody
self.dataStore.user.summonsMessageSignature = customCallMessageSignature
self.dataStore.user.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods
self.dataStore.saveUser()
}
@ -142,16 +154,22 @@ struct CallMessageCustomizationView: View {
if user.summonsUseFullCustomMessage == false {
HStack {
Spacer()
FooterButtonView("éditer") {
focusedField = .body
FooterButtonView("effacer") {
customCallMessageBody = ""
_save()
}
Divider()
FooterButtonView("défaut") {
customCallMessageBody = ContactType.defaultCustomMessage
_save()
}
Divider()
FooterButtonView("éditer") {
focusedField = .body
}
}
if user.summonsUseFullCustomMessage {
Section {
LazyHStack {
} else {
LazyVGrid(columns: columns, spacing: 0) {
FooterButtonView("#titre") {
customCallMessageBody.append("#titre")
focusedField = .body
@ -181,8 +199,6 @@ struct CallMessageCustomizationView: View {
focusedField = .body
}
}
} header: {
Text("Utilisez ces balises")
}
}
}
@ -275,6 +291,16 @@ struct CallMessageCustomizationView: View {
} footer: {
HStack {
Spacer()
FooterButtonView("effacer") {
summonsAvailablePaymentMethods = ""
_save()
}
Divider()
FooterButtonView("défaut") {
summonsAvailablePaymentMethods = ContactType.defaultAvailablePaymentMethods
_save()
}
Divider()
FooterButtonView("éditer") {
focusedField = .paymentMethods
}

@ -6,11 +6,13 @@
//
import SwiftUI
import LeStorage
struct CallSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
@State private var showSendToAllView: Bool = false
@State private var addLink: Bool = false
var body: some View {
List {
@ -29,6 +31,27 @@ struct CallSettingsView: View {
}
}
Section {
RowButtonView("Envoyer le lien du tournoi") {
addLink = true
}
.disabled(tournament.isPrivate)
} footer: {
if tournament.isPrivate {
Button {
tournament.isPrivate = false
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
} label: {
Text(.init("Le tournoi n'est pas visible sur [Padel Club](\(URLs.main.rawValue)), ")).foregroundStyle(.logoRed) + Text("le rendre visible ?").underline().foregroundStyle(.master)
}
}
}
#if DEBUG
Section {
RowButtonView("Annuler toutes les convocations", role: .destructive) {
let teams = tournament.unsortedTeams()
@ -48,9 +71,14 @@ struct CallSettingsView: View {
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: teams)
}
}
#endif
}
.sheet(isPresented: $showSendToAllView) {
SendToAllView()
SendToAllView(addLink: false)
.tint(.master)
}
.sheet(isPresented: $addLink) {
SendToAllView(addLink: true)
.tint(.master)
}
}

@ -81,11 +81,15 @@ struct CallView: View {
}
var finalMessage: String {
ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat)
ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat, reSummon: reSummon)
}
var reSummon: Bool {
teams.allSatisfy({ $0.called() })
}
var body: some View {
let callWord = teams.allSatisfy({ $0.called() }) ? "Reconvoquer" : "Convoquer"
let callWord = reSummon ? "Reconvoquer" : "Convoquer"
HStack {
if teams.count == 1 {
if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame {

@ -16,7 +16,7 @@ struct SendToAllView: View {
@State private var contactMethod: Int = 1
@State private var contactRecipients: Set<String> = Set()
@State private var sentError: ContactManagerError? = nil
let addLink: Bool
@State var cannotPayForTournament: Bool = false
var messageSentFailed: Binding<Bool> {
@ -72,16 +72,16 @@ struct SendToAllView: View {
Section {
RowButtonView("Contacter \(_totalString())") {
if contactMethod == 0 {
contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: tournament.tournamentTitle(), tournamentBuild: nil)
contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: addLink ? tournament.shareURL()?.absoluteString : nil, tournamentBuild: nil)
} else {
contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: nil, subject: tournament.tournamentTitle(), tournamentBuild: nil)
contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: addLink ? tournament.shareURL()?.absoluteString : nil, subject: tournament.tournamentTitle(), tournamentBuild: nil)
}
}
}
}
.environment(\.editMode, Binding.constant(EditMode.active))
.headerProminence(.increased)
.navigationTitle("Réglages")
.navigationTitle("Préparation")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.alert("Un problème est survenu", isPresented: messageSentFailed) {
@ -169,5 +169,5 @@ struct SendToAllView: View {
}
#Preview {
SendToAllView()
SendToAllView(addLink: true)
}

@ -152,7 +152,7 @@ struct CashierView: View {
.searchable(text: $searchText, isPresented: $isSearching, prompt: Text("Chercher un joueur"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: _sharedData())
ShareLink(item: _sharedData().createTxtFile("bilan"))
}
}
}

@ -32,6 +32,11 @@ struct GroupStageSettingsView: View {
Section {
RowButtonView("Valider les poules", role: .destructive) {
tournament.shouldVerifyGroupStage = false
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.")

@ -281,70 +281,37 @@ struct MatchDetailView: View {
}
.tint(.master)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
if match.courtIndex != nil {
Button(role: .destructive) {
match.removeCourt()
save()
} label: {
Text("Supprimer le terrain")
}
}
Button(role: .destructive) {
match.startDate = nil
match.endDate = nil
save()
} label: {
Text("Supprimer l'horaire")
}
// .refreshable {
// if match.isBroadcasted() {
// match.refreshBroadcast()
// }
// }
// .toolbar {
// ToolbarItem(placement: .topBarTrailing) {
// Menu {
// Button {
// scoreType = .live
// } label: {
// Label("Saisie Live", systemImage: "airplayaudio.circle")
// }
//
// Button {
// scoreType = .prepare
// } label: {
// Label("Préparer", systemImage: "calendar")
// }
//
// Divider()
// Menu {
// if match.fieldIndex > 0 {
// Button(role: .destructive) {
// match.currentTournament?.removeField(match.fieldIndex)
// match.fieldIndex = 0
// match.refreshBroadcast()
// save()
// } label: {
// Label("Supprimer le terrain", systemImage: "figure.run")
// }
// }
// Button(role: .destructive) {
// match.restartMatch()
// save()
// } label: {
// Label("Supprimer l'horaire", systemImage: "xmark.circle.fill")
// }
//
// Button(role: .destructive) {
// match.resetScore()
// save()
// } label: {
// Label("Supprimer les scores", systemImage: "xmark.circle.fill")
// }
//
// if match.isFederalTournament == false && match.isFriendlyMatch == false {
// Button(role: .destructive) {
// match.resetMatch()
// save()
// } label: {
// Label("Supprimer les équipes et les scores", systemImage: "xmark.circle.fill")
// }
// }
// } label: {
// Text("Éditer")
// }
//
// } label: {
// Label("Options", systemImage: "ellipsis.circle")
// }
// }
// }
Button(role: .destructive) {
match.resetScores()
save()
} label: {
Text("Supprimer les scores")
}
} label: {
LabelOptions()
}
}
}
.navigationTitle(match.matchTitle())
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
@ -365,8 +332,6 @@ struct MatchDetailView: View {
@ViewBuilder
var menuView: some View {
broadcastView
if match.hasStarted() {
Section {
editionView
@ -455,23 +420,10 @@ struct MatchDetailView: View {
}
}
// if match.canBroadcast() == true {
// Picker(selection: $broadcasted) {
// Text("Oui").tag(true)
// Text("Non").tag(false)
// } label: {
// Text("Diffuser automatiquement")
// }
// }
RowButtonView("Valider") {
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
if broadcasted {
broadcastAndSave()
} else {
save()
}
isEditing.toggle()
@ -481,22 +433,6 @@ struct MatchDetailView: View {
}
}
@ViewBuilder
var broadcastView: some View {
Section {
// if match.isBroadcasted() {
// RowButtonView("Arrêter de diffuser") {
// match.stopBroadcast()
// save()
// }
// } else if match.canBroadcast() == true {
// RowButtonView("Diffuser", systemImage: "airplayvideo") {
// broadcastAndSave()
// }
// }
}
}
var shareView: some View {
NavigationLink {
//EditSharingView(match: match)
@ -509,16 +445,6 @@ struct MatchDetailView: View {
private func save() {
try? dataStore.matches.addOrUpdate(instance: match)
}
private func broadcastAndSave() {
Task {
//try? await match.broadcast()
await MainActor.run {
}
}
}
}
#Preview {

@ -29,12 +29,12 @@ struct MatchSummaryView: View {
if let groupStage = match.groupStageObject {
self.roundTitle = groupStage.groupStageTitle()
} else if let round = match.roundObject {
self.roundTitle = round.roundTitle()
self.roundTitle = round.roundTitle(.short)
} else {
self.roundTitle = nil
}
self.matchTitle = match.matchTitle()
self.matchTitle = match.matchTitle(.short)
if let court = match.courtName(), match.hasEnded() == false {
self.courtName = court

@ -85,7 +85,7 @@ struct CalendarView: View {
ForEach(tournamentsByDay, id: \.holderId) { tournamentHolder in
if let tournament = tournamentHolder as? Tournament {
Section {
Button(tournament.tournamentTitle()) {
Button(tournament.tournamentTitle(.short)) {
navigation.path.append(tournament)
}
} header: {

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct OngoingView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@ -16,20 +17,42 @@ struct OngoingView: View {
var matches: [Match] {
let sorting = sortByField ? fieldSorting : defaultSorting
return dataStore.matches.filter({ $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending)
let now = Date()
return dataStore.matches.filter({ $0.startDate != nil && $0.startDate! < now && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending)
}
var body: some View {
@Bindable var navigation = navigation
NavigationStack(path: $navigation.ongoingPath) {
let matches = matches
let matches = matches.filter { $0.currentTournament()?.isDeleted == false }
List {
ForEach(matches) { match in
Section {
MatchRowView(match: match, matchViewStyle: .standardStyle)
} header: {
if let tournament = match.currentTournament() {
Text(tournament.tournamentTitle())
HStack {
Text(tournament.tournamentTitle(.short))
Spacer()
if let club = tournament.club() {
Text("@" + club.clubTitle(.short))
}
}
} else {
Text("Pas de tournoi")
}
} footer: {
HStack {
if let tournament = match.currentTournament() {
Text(tournament.eventLabel())
}
#if DEBUG
Spacer()
FooterButtonView("copier l'id") {
let pasteboard = UIPasteboard.general
pasteboard.string = match.id
}
#endif
}
}
}
@ -51,6 +74,16 @@ struct OngoingView: View {
} label: {
}
#if DEBUG
Button("effacer les mauvais matchs") {
let bad = matches.filter({ $0.currentTournament() == nil })
do {
try dataStore.matches.delete(contentOfs: bad)
} catch {
Logger.error(error)
}
}
#endif
//todo
//presentFilterView.toggle()
} label: {

@ -34,7 +34,7 @@ struct TournamentOrganizerView: View {
HStack {
ScrollView(.horizontal) {
HStack {
ForEach(dataStore.tournaments) { tournament in
ForEach(dataStore.tournaments.filter({ $0.isDeleted == false })) { tournament in
TournamentButtonView(tournament: tournament)
}
}

@ -6,14 +6,57 @@
//
import SwiftUI
import LeStorage
struct ToolboxView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
var body: some View {
@Bindable var navigation = navigation
NavigationStack(path: $navigation.toolboxPath) {
List {
#if DEBUG
Section {
RowButtonView("Fix Names") {
let playerRegistrations = dataStore.playerRegistrations
playerRegistrations.forEach { player in
player.firstName = player.firstName.trimmed.capitalized
player.lastName = player.lastName.trimmed.uppercased()
}
do {
try dataStore.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations)
} catch {
Logger.error(error)
}
}
}
Section {
RowButtonView("Delete teams") {
let teamRegistrations = dataStore.teamRegistrations.filter({ $0.tournamentObject() == nil })
do {
try dataStore.teamRegistrations.delete(contentOfs: teamRegistrations)
} catch {
Logger.error(error)
}
}
}
Section {
RowButtonView("Delete players") {
let playersRegistrations = dataStore.playerRegistrations.filter({ $0.team() == nil })
do {
try dataStore.playerRegistrations.delete(contentOfs: playersRegistrations)
} catch {
Logger.error(error)
}
}
}
#endif
Section {
NavigationLink {
SelectablePlayerListView()

@ -30,6 +30,7 @@ struct SchedulerView: View {
List {
switch destination {
case .scheduleGroupStage:
Section {
MatchFormatPickingView(matchFormat: $tournament.groupStageMatchFormat) {
Task {
tournament.matchScheduler()?.updateSchedule(tournament: tournament)
@ -48,6 +49,16 @@ struct SchedulerView: View {
}
}
} footer: {
if tournament.groupStageMatchFormat.weight > tournament.groupStageSmartMatchFormat().weight {
Button {
tournament.groupStageMatchFormat = tournament.groupStageSmartMatchFormat()
} label: {
Text("devrait être joué au moins en " + tournament.groupStageSmartMatchFormat().format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
}
.buttonStyle(.plain)
}
}
ForEach(tournament.groupStages()) {
GroupStageScheduleEditorView(groupStage: $0, tournament: tournament)
@ -63,6 +74,19 @@ struct SchedulerView: View {
}
.headerProminence(.increased)
.monospacedDigit()
.onChange(of: tournament.groupStageMatchFormat) {
let groupStages = tournament.groupStages()
tournament.groupStages().forEach { groupStage in
groupStage.updateMatchFormat(tournament.groupStageMatchFormat)
}
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
try dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
} catch {
Logger.error(error)
}
}
}
@ViewBuilder
@ -92,8 +116,24 @@ struct SchedulerView: View {
}
} header: {
Text(round.titleLabel())
} footer: {
let federalFormat = tournament.roundSmartMatchFormat(round.index)
if round.matchFormat.weight > federalFormat.weight {
Button {
round.updateMatchFormatAndAllMatches(federalFormat)
do {
try dataStore.rounds.addOrUpdate(instance: round)
} catch {
Logger.error(error)
}
} label: {
Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
}
.buttonStyle(.plain)
}
}
if round.index != 0 {
Section {
NavigationLink {
LoserRoundScheduleEditorView(upperRound: round, tournament: tournament)
@ -119,6 +159,24 @@ struct SchedulerView: View {
}
} header: {
Text("Match de classement \(round.roundTitle(.short))")
} footer: {
if round.index == 1, let semi = round.loserRounds().first {
let federalFormat = tournament.loserBracketSmartMatchFormat(1)
if semi.matchFormat.weight > federalFormat.weight {
Button {
round.updateMatchFormatAndAllMatches(federalFormat)
do {
try dataStore.rounds.addOrUpdate(instance: round)
} catch {
Logger.error(error)
}
} label: {
Text("devrait être joué au moins en " + federalFormat.format + ", ") + Text("modifier ?").underline().foregroundStyle(.master)
}
.buttonStyle(.plain)
}
}
}
}
}
}

@ -32,7 +32,6 @@ struct PlayerPopoverView: View {
@State private var source: String?
init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [.firstName, .lastName], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) {
let source = source
if let source {
let words = source.components(separatedBy: .whitespaces)
if words.isEmpty == false {
@ -44,6 +43,8 @@ struct PlayerPopoverView: View {
} else {
_firstName = State(wrappedValue: source)
}
_source = State(wrappedValue: source)
}
_sex = State(wrappedValue: sex)
@ -51,7 +52,6 @@ struct PlayerPopoverView: View {
self.requiredField = requiredField
self.creationCompletionHandler = creationCompletionHandler
_source = State(wrappedValue: source)
}
var body: some View {
@ -94,6 +94,7 @@ struct PlayerPopoverView: View {
Spacer()
TextField("Prénom", text: $firstName)
.submitLabel(.next)
.autocorrectionDisabled()
.keyboardType(.alphabet)
.textInputAutocapitalization(.words)
.focused($firstNameIsFocused)
@ -108,6 +109,7 @@ struct PlayerPopoverView: View {
Spacer()
TextField("Nom", text: $lastName)
.submitLabel(.next)
.autocorrectionDisabled()
.textInputAutocapitalization(.words)
.keyboardType(.alphabet)
.focused($lastNameIsFocused)
@ -180,7 +182,6 @@ struct PlayerPopoverView: View {
.onAppear {
firstNameIsFocused = true
}
.autocorrectionDisabled()
.navigationTitle(sex == 1 ? "Nouveau joueur" : "Nouvelle joueuse")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
@ -199,8 +200,10 @@ struct PlayerPopoverView: View {
}
}
if amountIsFocused || licenseIsFocused {
if licenseIsFocused || amountIsFocused {
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button("Confirmer") {
if licenseIsFocused {
license = license.trimmed
@ -217,6 +220,8 @@ struct PlayerPopoverView: View {
amountIsFocused = false
}
}
.buttonStyle(.bordered)
}
}
}
}

@ -46,6 +46,9 @@ struct PlayerDetailView: View {
.focused($textFieldIsFocus)
} label: {
Text("Rang")
if player.rank == nil {
Text("Classement calculé : " + player.computedRank.formatted())
}
}
} header: {
Text("Classement actuel")

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct RoundSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@ -18,6 +19,11 @@ struct RoundSettingsView: View {
Section {
RowButtonView("Valider le tableau", role: .destructive) {
tournament.shouldVerifyBracket = false
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.")

@ -28,7 +28,7 @@ struct LearnMoreSheetView: View {
.foregroundStyle(.secondary)
ShareLink(item: tournament.pasteDataForImporting()) {
ShareLink(item: tournament.pasteDataForImporting().createTxtFile(tournament.tournamentTitle(.short))) {
HStack {
Spacer()
Text("Exporter les inscriptions")

@ -9,11 +9,18 @@ import SwiftUI
import LeStorage
struct TournamentGeneralSettingsView: View {
@Environment(Tournament.self) private var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
var tournament: Tournament
@State private var tournamentName: String = ""
@FocusState private var textFieldIsFocus: Bool
@State private var entryFee: Double? = nil
@FocusState private var focusedField: Tournament.CodingKeys?
init(tournament: Tournament) {
self.tournament = tournament
_tournamentName = State(wrappedValue: tournament.name ?? "")
_entryFee = State(wrappedValue: tournament.entryFee)
}
var body: some View {
@Bindable var tournament = tournament
@ -29,44 +36,41 @@ struct TournamentGeneralSettingsView: View {
Section {
LabeledContent {
TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $tournament.entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
.focused($textFieldIsFocus)
.focused($focusedField, equals: ._entryFee)
} label: {
Text("Inscription")
}
}
Section {
LabeledContent {
TextField("Nom", text: $tournamentName)
.multilineTextAlignment(.trailing)
TextField("Nom du tournoi", text: $tournamentName, axis: .vertical)
.lineLimit(2)
.frame(maxWidth: .infinity)
.keyboardType(.alphabet)
.autocorrectionDisabled()
.onSubmit {
if tournamentName.trimmed.isEmpty {
tournament.name = nil
} else {
tournament.name = tournamentName
}
}
} label: {
Text("Nom du tournoi")
}
.focused($focusedField, equals: ._name)
}
}
.scrollDismissesKeyboard(.immediately)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
if textFieldIsFocus {
if focusedField != nil {
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button("Valider") {
textFieldIsFocus = false
if focusedField == ._name {
if tournamentName.trimmed.isEmpty {
tournament.name = nil
} else {
tournament.name = tournamentName
}
} else if focusedField == ._entryFee {
tournament.entryFee = entryFee
}
focusedField = nil
}
.buttonStyle(.bordered)
}
@ -115,7 +119,3 @@ struct TournamentGeneralSettingsView: View {
}
}
}
#Preview {
TournamentGeneralSettingsView()
}

@ -120,18 +120,22 @@ struct InscriptionManagerView: View {
}
}
.onAppear {
self.presentationCount += 1
if self.teamsHash == nil {
self.teamsHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
}
}
.onDisappear {
self.presentationCount -= 1
if self.presentationCount == 0 {
let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
if let teamsHash {
self.tournament.shouldVerifyBracket = newHash != teamsHash
self.tournament.shouldVerifyGroupStage = newHash != teamsHash
if let teamsHash, newHash != teamsHash {
self.teamsHash = newHash
if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
self.tournament.shouldVerifyBracket = true
self.tournament.shouldVerifyGroupStage = true
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
}
@ -161,6 +165,7 @@ struct InscriptionManagerView: View {
}
.sheet(isPresented: $presentPlayerCreation) {
PlayerPopoverView(source: _searchSource(), sex: _addPlayerSex()) { p in
p.setComputedRank(in: tournament)
createdPlayers.insert(p)
createdPlayerIds.insert(p.id)
}
@ -249,7 +254,7 @@ struct InscriptionManagerView: View {
Label("Clôturer", systemImage: "lock")
}
Divider()
ShareLink(item: tournament.pasteDataForImporting()) {
ShareLink(item: tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))) {
Label("Exporter les paires", systemImage: "square.and.arrow.up")
}
Button {
@ -531,6 +536,23 @@ struct InscriptionManagerView: View {
private func _informationView(count: Int) -> some View {
Section {
let unsortedTeams = tournament.unsortedTeams()
let walkoutTeams = tournament.walkoutTeams()
LabeledContent {
Text(unsortedTeams.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
} label: {
Text("Paire\(unsortedTeams.count.pluralSuffix) inscrite\(unsortedTeams.count.pluralSuffix)")
Text("dont \(walkoutTeams.count) forfait\(walkoutTeams.count.pluralSuffix)")
}
let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
LabeledContent {
Text(max(0, unsortedTeamsWithoutWO.count - tournament.teamCount).formatted()).font(.largeTitle)
} label: {
Text("Liste d'attente")
}
NavigationLink {
InscriptionInfoView()
.environment(tournament)
@ -544,8 +566,6 @@ struct InscriptionManagerView: View {
}
}
}
} header: {
Text(count.formatted() + "/" + tournament.teamCount.formatted() + " paires inscrites")
}
}
@ -849,6 +869,11 @@ struct InscriptionManagerView: View {
private func _teamMenuOptionView(_ team: TeamRegistration) -> some View {
Menu {
Section {
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = team.playersPasteData()
}
Divider()
Button("Changer les joueurs") {
editedTeam = team
team.unsortedPlayers().forEach { player in
@ -906,6 +931,7 @@ struct InscriptionManagerView: View {
} label: {
LabelDelete()
}
// } header: {
// Text(team.teamLabel(.short))
}

@ -67,7 +67,7 @@ struct TournamentSettingsView: View {
case .matchFormats:
TournamentMatchFormatsSettingsView()
case .general:
TournamentGeneralSettingsView()
TournamentGeneralSettingsView(tournament: tournament)
case .club:
TournamentClubSettingsView()
}

@ -71,7 +71,7 @@ struct TournamentCellView: View {
Spacer()
if let tournament = tournament as? Tournament, displayStyle == .wide {
let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted()
let count = hasStarted ? tournament.selectedSortedTeams().count : tournament.unsortedTeams().count
let count = hasStarted ? tournament.selectedSortedTeams().count : tournament.unsortedTeamsWithoutWO().count
Text(count.formatted())
} else if let federalTournament = tournament as? FederalTournament {
Button {

@ -17,7 +17,7 @@ struct TournamentInscriptionView: View {
Section {
NavigationLink(value: Screen.inscription) {
LabeledContent {
Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted())
Text(tournament.unsortedTeamsWithoutWO().count.formatted() + "/" + tournament.teamCount.formatted())
} label: {
Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {

@ -121,7 +121,7 @@ struct TournamentView: View {
.toolbar {
ToolbarItem(placement: .principal) {
VStack(spacing: -4.0) {
Text(tournament.tournamentTitle()).font(.headline)
Text(tournament.tournamentTitle(.short)).font(.headline)
Text(tournament.formattedDate())
.font(.subheadline).foregroundStyle(.secondary)
}

@ -96,7 +96,7 @@ final class ServerDataTests: XCTestCase {
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, 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, shouldVerifyBracket: true, shouldVerifyGroupStage: true)
let t = try await Store.main.service().post(tournament)
assert(t.event == tournament.event)
@ -130,7 +130,8 @@ final class ServerDataTests: XCTestCase {
assert(t.publishSummons == tournament.publishSummons)
assert(t.publishGroupStages == tournament.publishGroupStages)
assert(t.publishBrackets == tournament.publishBrackets)
assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket)
assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage)
}
func testGroupStage() async throws {

Loading…
Cancel
Save