Laurent 1 year ago
commit a37ac62968
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 8
      PadelClub/Data/Club.swift
  3. 1
      PadelClub/Data/GroupStage.swift
  4. 12
      PadelClub/Data/Match.swift
  5. 4
      PadelClub/Data/MatchScheduler.swift
  6. 10
      PadelClub/Data/TeamRegistration.swift
  7. 10
      PadelClub/Data/TeamScore.swift
  8. 7
      PadelClub/Data/Tournament.swift
  9. 7
      PadelClub/PadelClubApp.swift
  10. 8
      PadelClub/Utils/PadelRule.swift
  11. 23
      PadelClub/Views/Calling/Components/MenuWarningView.swift
  12. 16
      PadelClub/Views/Cashier/CashierDetailView.swift
  13. 9
      PadelClub/Views/Cashier/CashierView.swift
  14. 8
      PadelClub/Views/Club/ClubDetailView.swift
  15. 2
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  16. 5
      PadelClub/Views/GroupStage/GroupStageView.swift
  17. 5
      PadelClub/Views/Match/Components/MatchDateView.swift
  18. 27
      PadelClub/Views/Match/MatchDetailView.swift
  19. 8
      PadelClub/Views/Navigation/MainView.swift
  20. 2
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  21. 10
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  22. 41
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  23. 54
      PadelClub/Views/Planning/PlanningSettingsView.swift
  24. 159
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  25. 7
      PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift
  26. 11
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  27. 5
      PadelClub/Views/Tournament/Screen/PrintSettingsView.swift
  28. 22
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  29. 10
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  30. 7
      PadelClub/Views/Tournament/TournamentInitView.swift
  31. 12
      PadelClubTests/ServerDataTests.swift

@ -1935,7 +1935,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 = 25; CURRENT_PROJECT_VERSION = 28;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1973,7 +1973,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 = 25; CURRENT_PROJECT_VERSION = 28;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -178,6 +178,14 @@ extension Club {
return URL(string: "https://tenup.fft.fr/club/\(code)") return URL(string: "https://tenup.fft.fr/club/\(code)")
} }
func courtName(atIndex courtIndex: Int) -> String {
courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
}
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
customizedCourts.first(where: { $0.index == courtIndex })?.name
}
func update(fromClub club: Club) { func update(fromClub club: Club) {
self.acronym = club.acronym self.acronym = club.acronym
self.name = club.name self.name = club.name

@ -179,7 +179,6 @@ class GroupStage: ModelObject, Storable {
} }
func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] { func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] {
return []
return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false })
} }

@ -36,8 +36,9 @@ class Match: ModelObject, Storable {
//var order: Int //var order: Int
var disabled: Bool = false var disabled: Bool = false
private(set) var courtIndex: Int? private(set) var courtIndex: Int?
var confirmed: Bool = false
internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil) { internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil, confirmed: Bool = false) {
self.round = round self.round = round
self.groupStage = groupStage self.groupStage = groupStage
self.startDate = startDate self.startDate = startDate
@ -51,6 +52,7 @@ class Match: ModelObject, Storable {
self.disabled = disabled self.disabled = disabled
self.name = name self.name = name
self.courtIndex = courtIndex self.courtIndex = courtIndex
self.confirmed = confirmed
// self.broadcasted = broadcasted // self.broadcasted = broadcasted
// self.order = order // self.order = order
} }
@ -162,6 +164,7 @@ class Match: ModelObject, Storable {
func cleanScheduleAndSave(_ targetStartDate: Date? = nil) { func cleanScheduleAndSave(_ targetStartDate: Date? = nil) {
startDate = targetStartDate startDate = targetStartDate
confirmed = targetStartDate == nil ? false : true
endDate = nil endDate = nil
followingMatch()?.cleanScheduleAndSave(nil) followingMatch()?.cleanScheduleAndSave(nil)
_loserMatch()?.cleanScheduleAndSave(nil) _loserMatch()?.cleanScheduleAndSave(nil)
@ -548,6 +551,8 @@ class Match: ModelObject, Storable {
startDate = fromStartDate startDate = fromStartDate
endDate = toEndDate endDate = toEndDate
} }
confirmed = true
} }
func courtName() -> String? { func courtName() -> String? {
@ -734,7 +739,7 @@ class Match: ModelObject, Storable {
} }
func isRunning() -> Bool { // at least a match has started func isRunning() -> Bool { // at least a match has started
hasStarted() && hasEnded() == false confirmed && hasStarted() && hasEnded() == false
} }
func hasStarted() -> Bool { // meaning at least one match is over func hasStarted() -> Bool { // meaning at least one match is over
@ -785,6 +790,7 @@ class Match: ModelObject, Storable {
case _name = "name" case _name = "name"
// case _order = "order" // case _order = "order"
case _disabled = "disabled" case _disabled = "disabled"
case _confirmed = "confirmed"
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {
@ -855,6 +861,8 @@ class Match: ModelObject, Storable {
} else { } else {
try container.encodeNil(forKey: ._courtIndex) try container.encodeNil(forKey: ._courtIndex)
} }
try container.encode(confirmed, forKey: ._confirmed)
} }
func insertOnServer() throws { func insertOnServer() throws {

@ -90,6 +90,7 @@ class MatchScheduler : ModelObject, Storable {
matches.forEach({ matches.forEach({
$0.removeCourt() $0.removeCourt()
$0.startDate = nil $0.startDate = nil
$0.confirmed = false
}) })
var lastDate : Date = tournament.startDate var lastDate : Date = tournament.startDate
@ -565,6 +566,7 @@ class MatchScheduler : ModelObject, Storable {
if (roundId == nil && matchId == nil) || $0.startDate?.isEarlierThan(startDate) == false { if (roundId == nil && matchId == nil) || $0.startDate?.isEarlierThan(startDate) == false {
$0.startDate = nil $0.startDate = nil
$0.removeCourt() $0.removeCourt()
$0.confirmed = false
} }
}) })
@ -590,6 +592,7 @@ class MatchScheduler : ModelObject, Storable {
flattenedMatches[index...].forEach { flattenedMatches[index...].forEach {
$0.startDate = nil $0.startDate = nil
$0.removeCourt() $0.removeCourt()
$0.confirmed = false
} }
} }
} else if let roundId { } else if let roundId {
@ -598,6 +601,7 @@ class MatchScheduler : ModelObject, Storable {
flattenedMatches[index...].forEach { flattenedMatches[index...].forEach {
$0.startDate = nil $0.startDate = nil
$0.removeCourt() $0.removeCourt()
$0.confirmed = false
} }
} }
} }

@ -69,6 +69,16 @@ class TeamRegistration: ModelObject, Storable {
try Store.main.deleteDependencies(items: self.unsortedPlayers()) try Store.main.deleteDependencies(items: self.unsortedPlayers())
} }
func hasArrived() {
let unsortedPlayers = unsortedPlayers()
unsortedPlayers.forEach({ $0.hasArrived = true })
do {
try DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers)
} catch {
Logger.error(error)
}
}
func isSeedable() -> Bool { func isSeedable() -> Bool {
bracketPosition == nil && groupStage == nil bracketPosition == nil && groupStage == nil
} }

@ -17,15 +17,15 @@ class TeamScore: ModelObject, Storable {
var id: String = Store.randomId() var id: String = Store.randomId()
var match: String var match: String
var teamRegistration: String? var teamRegistration: String?
var playerRegistrations: [String] = [] //var playerRegistrations: [String] = []
var score: String? var score: String?
var walkOut: Int? var walkOut: Int?
var luckyLoser: Int? var luckyLoser: Int?
init(match: String, teamRegistration: String? = nil, playerRegistrations: [String], score: String? = nil, walkOut: Int? = nil, luckyLoser: Int? = nil) { init(match: String, teamRegistration: String? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Int? = nil) {
self.match = match self.match = match
self.teamRegistration = teamRegistration self.teamRegistration = teamRegistration
self.playerRegistrations = playerRegistrations // self.playerRegistrations = playerRegistrations
self.score = score self.score = score
self.walkOut = walkOut self.walkOut = walkOut
self.luckyLoser = luckyLoser self.luckyLoser = luckyLoser
@ -65,7 +65,7 @@ class TeamScore: ModelObject, Storable {
case _id = "id" case _id = "id"
case _match = "match" case _match = "match"
case _teamRegistration = "teamRegistration" case _teamRegistration = "teamRegistration"
case _playerRegistrations = "playerRegistrations" //case _playerRegistrations = "playerRegistrations"
case _score = "score" case _score = "score"
case _walkOut = "walkOut" case _walkOut = "walkOut"
case _luckyLoser = "luckyLoser" case _luckyLoser = "luckyLoser"
@ -83,7 +83,7 @@ class TeamScore: ModelObject, Storable {
try container.encodeNil(forKey: ._teamRegistration) try container.encodeNil(forKey: ._teamRegistration)
} }
try container.encode(playerRegistrations, forKey: ._playerRegistrations) //try container.encode(playerRegistrations, forKey: ._playerRegistrations)
if let score = score { if let score = score {
try container.encode(score, forKey: ._score) try container.encode(score, forKey: ._score)

@ -50,6 +50,7 @@ class Tournament : ModelObject, Storable {
var publishBrackets: Bool = false var publishBrackets: Bool = false
var shouldVerifyGroupStage: Bool = false var shouldVerifyGroupStage: Bool = false
var shouldVerifyBracket: Bool = false var shouldVerifyBracket: Bool = false
var hideTeamsWeight: Bool = false
@ObservationIgnored @ObservationIgnored
var navigationPath: [Screen] = [] var navigationPath: [Screen] = []
@ -94,9 +95,10 @@ class Tournament : ModelObject, Storable {
case _publishBrackets = "publishBrackets" case _publishBrackets = "publishBrackets"
case _shouldVerifyGroupStage = "shouldVerifyGroupStage" case _shouldVerifyGroupStage = "shouldVerifyGroupStage"
case _shouldVerifyBracket = "shouldVerifyBracket" case _shouldVerifyBracket = "shouldVerifyBracket"
case _hideTeamsWeight = "hideTeamsWeight"
} }
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) { 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, hideTeamsWeight: Bool = false) {
self.event = event self.event = event
self.name = name self.name = name
self.startDate = startDate self.startDate = startDate
@ -130,6 +132,7 @@ class Tournament : ModelObject, Storable {
self.publishGroupStages = publishGroupStages self.publishGroupStages = publishGroupStages
self.shouldVerifyBracket = shouldVerifyBracket self.shouldVerifyBracket = shouldVerifyBracket
self.shouldVerifyGroupStage = shouldVerifyGroupStage self.shouldVerifyGroupStage = shouldVerifyGroupStage
self.hideTeamsWeight = hideTeamsWeight
} }
required init(from decoder: Decoder) throws { required init(from decoder: Decoder) throws {
@ -170,6 +173,7 @@ class Tournament : ModelObject, Storable {
publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false
shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false
shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false
hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false
} }
fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter() fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter()
@ -281,6 +285,7 @@ class Tournament : ModelObject, Storable {
try container.encode(publishGroupStages, forKey: ._publishGroupStages) try container.encode(publishGroupStages, forKey: ._publishGroupStages)
try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket) try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket)
try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage)
try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight)
} }
fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws { fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws {

@ -15,6 +15,13 @@ struct PadelClubApp: App {
@State private var navigationViewModel = NavigationViewModel() @State private var navigationViewModel = NavigationViewModel()
@StateObject var networkMonitor: NetworkMonitor = NetworkMonitor() @StateObject var networkMonitor: NetworkMonitor = NetworkMonitor()
static var appVersion: String {
let dictionary = Bundle.main.infoDictionary!
let version = dictionary["CFBundleShortVersionString"] as! String
let build = dictionary["CFBundleVersion"] as! String
return "\(version) (\(build))"
}
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
MainView() MainView()

@ -1134,7 +1134,7 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable {
case .twoSetsSuperTie: case .twoSetsSuperTie:
return 80 return 80
case .twoSetsDecisivePointSuperTie: case .twoSetsDecisivePointSuperTie:
return 75 return 70
case .twoSetsOfFourGames: case .twoSetsOfFourGames:
return 60 return 60
case .twoSetsOfFourGamesDecisivePoint: case .twoSetsOfFourGamesDecisivePoint:
@ -1142,11 +1142,11 @@ enum MatchFormat: Int, Hashable, Codable, CaseIterable {
case .nineGames: case .nineGames:
return 45 return 45
case .nineGamesDecisivePoint: case .nineGamesDecisivePoint:
return 35 return 40
case .megaTie: case .megaTie:
return 20 return 30
case .superTie: case .superTie:
return 15 return 25
} }
} }

@ -26,17 +26,24 @@ struct MenuWarningView: View {
// TODO: Guard // TODO: Guard
@ViewBuilder @ViewBuilder
private func _actionView(players: [PlayerRegistration], privateMode: Bool = false) -> some View { private func _actionView(players: [PlayerRegistration], privateMode: Bool = false) -> some View {
Menu { if players.count == 1, let player = players.first, let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {
ForEach(players) { player in Link(destination: url) {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") { Label("Appeler", systemImage: "phone")
Link(destination: url) { Text(number)
Text(player.playerLabel(.short)) }
Label(number, systemImage: "phone") } else {
Menu {
ForEach(players) { player in
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label(player.playerLabel(.short), systemImage: "phone")
Text(number)
}
} }
} }
} label: {
Text("Appeler un joueur")
} }
} label: {
Label("Appeler un joueur", systemImage: "phone")
} }
Button("Message") { Button("Message") {

@ -43,14 +43,16 @@ struct CashierDetailView: View {
DisclosureGroup { DisclosureGroup {
ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in ForEach(PlayerRegistration.PlayerPaymentType.allCases) { type in
let count = tournament.selectedPlayers().filter({ $0.paymentType == type }).count let count = tournament.selectedPlayers().filter({ $0.paymentType == type }).count
LabeledContent { if count > 0 {
if let entryFee = tournament.entryFee { LabeledContent {
let sum = Double(count) * entryFee if let entryFee = tournament.entryFee {
Text(sum.formatted(.currency(code: "EUR"))) let sum = Double(count) * entryFee
Text(sum.formatted(.currency(code: "EUR")))
}
} label: {
Text(type.localizedLabel())
Text(count.formatted())
} }
} label: {
Text(type.localizedLabel())
Text(count.formatted())
} }
} }
} label: { } label: {

@ -21,6 +21,9 @@ struct CashierView: View {
init(tournament: Tournament, teams: [TeamRegistration]) { init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament] self.tournaments = [tournament]
self.teams = teams self.teams = teams
if tournament.hasEnded(), tournament.players().anySatisfy({ $0.hasPaid() == false }) {
_filterOption = .init(wrappedValue: .didNotPay)
}
if teams.filter({ $0.callDate != nil }).isEmpty { if teams.filter({ $0.callDate != nil }).isEmpty {
_sortOption = .init(wrappedValue: .teamRank) _sortOption = .init(wrappedValue: .teamRank)
} else { } else {
@ -29,8 +32,8 @@ struct CashierView: View {
} }
private func _sharedData() -> String { private func _sharedData() -> String {
let players = teams let players = teams.filter({ _shouldDisplayTeam($0) })
.flatMap({ $0.players() }) .flatMap({ $0.players().filter({ _shouldDisplayPlayer($0) }) })
.map { .map {
[$0.pasteData()] [$0.pasteData()]
.compacted() .compacted()
@ -163,7 +166,7 @@ struct CashierView: View {
} }
private func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool { private func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool {
team.players().allSatisfy({ team.players().anySatisfy({
_shouldDisplayPlayer($0) _shouldDisplayPlayer($0)
}) })
} }

@ -142,6 +142,14 @@ struct ClubDetailView: View {
ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt) ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt)
if let padelClubLink = club.shareURL() {
Section {
Link(destination: padelClubLink) {
Text("Accéder au club sur le site Padel Club")
}
}
}
if let federalLink = club.federalLink() { if let federalLink = club.federalLink() {
Section { Section {
LabeledContent("Code Club") { LabeledContent("Code Club") {

@ -105,6 +105,7 @@ struct GroupStageSettingsView: View {
tournament.deleteGroupStages() tournament.deleteGroupStages()
tournament.buildGroupStages() tournament.buildGroupStages()
generationDone = true generationDone = true
tournament.shouldVerifyGroupStage = false
_save() _save()
} }
} }
@ -115,6 +116,7 @@ struct GroupStageSettingsView: View {
tournament.groupStageOrderingMode = mode tournament.groupStageOrderingMode = mode
tournament.refreshGroupStages() tournament.refreshGroupStages()
generationDone = true generationDone = true
tournament.shouldVerifyGroupStage = false
_save() _save()
} }
} }

@ -150,6 +150,11 @@ struct GroupStageView: View {
} }
} }
} }
.contextMenu {
Button("équipe présente") {
team.hasArrived()
}
}
} }
.listRowView(isActive: team.qualified, color: .master) .listRowView(isActive: team.qualified, color: .master)
} else { } else {

@ -29,10 +29,12 @@ struct MatchDateView: View {
if match.startDate == nil && isReady { if match.startDate == nil && isReady {
Button("Démarrer") { Button("Démarrer") {
match.startDate = Date() match.startDate = Date()
match.confirmed = true
_save() _save()
} }
Button("Échauffement") { Button("Échauffement") {
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date())
match.confirmed = true
_save() _save()
} }
} else { } else {
@ -40,6 +42,7 @@ struct MatchDateView: View {
Button("Démarrer maintenant") { Button("Démarrer maintenant") {
match.startDate = Date() match.startDate = Date()
match.endDate = nil match.endDate = nil
match.confirmed = true
_save() _save()
} }
} else { } else {
@ -69,7 +72,7 @@ struct MatchDateView: View {
if showPrefix { if showPrefix {
Text("en cours").font(.footnote).foregroundStyle(.secondary) Text("en cours").font(.footnote).foregroundStyle(.secondary)
} }
if match.isReady() { if match.isReady() && match.confirmed {
Text(startDate, style: .timer) Text(startDate, style: .timer)
.monospacedDigit() .monospacedDigit()
.foregroundStyle(Color.master) .foregroundStyle(Color.master)

@ -300,6 +300,7 @@ struct MatchDetailView: View {
Button(role: .destructive) { Button(role: .destructive) {
match.startDate = nil match.startDate = nil
match.endDate = nil match.endDate = nil
match.confirmed = false
save() save()
} label: { } label: {
Text("Supprimer l'horaire") Text("Supprimer l'horaire")
@ -409,21 +410,22 @@ struct MatchDetailView: View {
Picker(selection: $fieldSetup) { Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random) Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable) //Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
if let tournament = match.currentTournament() { if let club = match.currentTournament()?.club() {
ForEach(0..<club.courtCount, id: \.self) { courtIndex in
Text(club.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
} else if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex)) Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
} }
} else {
ForEach(0..<20, id: \.self) { courtIndex in
Text(Court.courtIndexedTitle(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
} }
} label: { } label: {
Text("Choix du terrain") Text("Choix du terrain")
} }
.contextMenu {
NavigationLink {
//FieldDrawView(match: match)
} label: {
Text("Tirage au sort visuel")
}
}
RowButtonView("Valider") { RowButtonView("Valider") {
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup) match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
@ -438,15 +440,6 @@ struct MatchDetailView: View {
} }
} }
var shareView: some View {
NavigationLink {
//EditSharingView(match: match)
} label: {
Text("Partage sur les réseaux sociaux")
}
}
private func save() { private func save() {
do { do {
try dataStore.matches.addOrUpdate(instance: match) try dataStore.matches.addOrUpdate(instance: match)

@ -44,6 +44,12 @@ struct MainView: View {
} }
)} )}
var matches: [Match] {
dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil })
}
var badgeText: Text? = Store.main.userName() == nil ? Text("!").font(.headline) : nil
var body: some View { var body: some View {
TabView(selection: selectedTabHandler) { TabView(selection: selectedTabHandler) {
ActivityView() ActivityView()
@ -52,10 +58,12 @@ struct MainView: View {
.tabItem(for: .tournamentOrganizer) .tabItem(for: .tournamentOrganizer)
OngoingView() OngoingView()
.tabItem(for: .ongoing) .tabItem(for: .ongoing)
.badge(matches.count)
ToolboxView() ToolboxView()
.tabItem(for: .toolbox) .tabItem(for: .toolbox)
UmpireView() UmpireView()
.tabItem(for: .umpire) .tabItem(for: .umpire)
.badge(badgeText)
// PadelClubView() // PadelClubView()
// .tabItem(for: .padelClub) // .tabItem(for: .padelClub)
} }

@ -18,7 +18,7 @@ struct OngoingView: View {
var matches: [Match] { var matches: [Match] {
let sorting = sortByField ? fieldSorting : defaultSorting let sorting = sortByField ? fieldSorting : defaultSorting
let now = Date() let now = Date()
return dataStore.matches.filter({ $0.startDate != nil && $0.startDate! < now && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending) return dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending)
} }
var body: some View { var body: some View {

@ -82,6 +82,12 @@ struct ToolboxView: View {
} }
#endif #endif
Section {
Link(destination: URLs.main.url) {
Text("Accéder au site Padel Club")
}
}
Section { Section {
NavigationLink { NavigationLink {
SelectablePlayerListView() SelectablePlayerListView()
@ -116,6 +122,10 @@ struct ToolboxView: View {
Section { Section {
Link("Accéder au guide de la compétition", destination: URLs.padelRules.url) Link("Accéder au guide de la compétition", destination: URLs.padelRules.url)
} }
Section {
Text("Version de l'application").badge(PadelClubApp.appVersion)
}
} }
.navigationTitle(TabDestination.toolbox.title) .navigationTitle(TabDestination.toolbox.title)
} }

@ -57,7 +57,16 @@ struct UmpireView: View {
NavigationLink { NavigationLink {
MainUserView() MainUserView()
} label: { } label: {
Label("Mon compte", systemImage: "person.fill") LabeledContent {
if Store.main.userName() != nil {
Text(dataStore.user.username)
} else {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.logoRed)
}
} label: {
Label("Mon compte", systemImage: "person.fill")
}
} }
let currentPlayerData = dataStore.user.currentPlayerData() let currentPlayerData = dataStore.user.currentPlayerData()
@ -75,17 +84,27 @@ struct UmpireView: View {
presentSearchView = true presentSearchView = true
} }
} }
} header: {
if currentPlayerData != nil {
Text("Ma fiche joueur")
}
} footer: { } footer: {
if dataStore.user.licenceId == nil { if dataStore.user.licenceId == nil {
Text("Si vous avez participé à un tournoi dans les 12 derniers mois, Padel Club peut vous retrouver.") Text("Si vous avez participé à un tournoi dans les 12 derniers mois, Padel Club peut vous retrouver.")
} else { } else if let currentPlayerData {
Button("supprimer", role: .destructive) { Menu {
dataStore.user.licenceId = nil Button {
self.dataStore.saveUser() UIPasteboard.general.string = currentPlayerData.formattedLicense()
} label: {
Text("Copier ma licence")
Text(currentPlayerData.formattedLicense())
}
Button("Supprimer ma fiche") {
dataStore.user.licenceId = nil
self.dataStore.saveUser()
}
} label: {
Text("options")
.foregroundStyle(Color.master)
.underline()
} }
} }
} }
@ -111,7 +130,7 @@ struct UmpireView: View {
if let _lastDataSourceDate { if let _lastDataSourceDate {
LabeledContent { LabeledContent {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.tint(.green) .foregroundStyle(.green)
} label: { } label: {
Text(_lastDataSourceDate.monthYearFormatted) Text(_lastDataSourceDate.monthYearFormatted)
Text("Classement mensuel utilisé") Text("Classement mensuel utilisé")
@ -181,11 +200,11 @@ struct UmpireView: View {
do { do {
try dataStore.clubs.addOrUpdate(instance: userClub) try dataStore.clubs.addOrUpdate(instance: userClub)
user.setUserClub(userClub) user.setUserClub(userClub)
self.dataStore.saveUser()
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} }
self.dataStore.saveUser()
} }
}) })
} }

@ -94,7 +94,41 @@ struct PlanningSettingsView: View {
Text("Voir les options avancées") Text("Voir les options avancées")
} }
let allMatches = tournament.allMatches()
let allGroupStages = tournament.groupStages()
let allRounds = tournament.allRounds()
let matchesWithDate = allMatches.filter({ $0.startDate != nil })
let groupStagesWithDate = allGroupStages.filter({ $0.startDate != nil })
let roundsWithDate = allRounds.filter({ $0.startDate != nil })
if matchesWithDate.isEmpty == false || groupStagesWithDate.isEmpty == false || roundsWithDate.isEmpty == false {
Section {
RowButtonView("Supprimer tous les horaires", role: .destructive) {
do {
allMatches.forEach({
$0.startDate = nil
$0.confirmed = false
})
try dataStore.matches.addOrUpdate(contentOfs: allMatches)
allGroupStages.forEach({ $0.startDate = nil })
try dataStore.groupStages.addOrUpdate(contentOfs: allGroupStages)
allRounds.forEach({ $0.startDate = nil })
try dataStore.rounds.addOrUpdate(contentOfs: allRounds)
} catch {
Logger.error(error)
}
}
}
}
Section { Section {
if groupStagesWithDate.isEmpty == false {
Text("Des dates de démarrages ont été indiqué pour les poules et seront prises en compte.")
}
if roundsWithDate.isEmpty == false {
Text("Des dates de démarrages ont été indiqué pour les manches et seront prises en compte.")
}
RowButtonView("Horaire intelligent", role: .destructive) { RowButtonView("Horaire intelligent", role: .destructive) {
schedulingDone = false schedulingDone = false
await _setupSchedule() await _setupSchedule()
@ -104,26 +138,6 @@ struct PlanningSettingsView: View {
} footer: { } footer: {
Text("Padel Club programmera tous les matchs de votre tournoi en fonction de différents paramètres, ") + Text("tout en tenant compte des horaires que vous avez fixé.").underline() Text("Padel Club programmera tous les matchs de votre tournoi en fonction de différents paramètres, ") + Text("tout en tenant compte des horaires que vous avez fixé.").underline()
} }
Section {
RowButtonView("Supprimer tous les horaires", role: .destructive) {
do {
let allMatches = tournament.allMatches()
allMatches.forEach({ $0.startDate = nil })
try dataStore.matches.addOrUpdate(contentOfs: allMatches)
let allGroupStages = tournament.groupStages()
allGroupStages.forEach({ $0.startDate = nil })
try dataStore.groupStages.addOrUpdate(contentOfs: allGroupStages)
let allRounds = tournament.allRounds()
allRounds.forEach({ $0.startDate = nil })
try dataStore.rounds.addOrUpdate(contentOfs: allRounds)
} catch {
Logger.error(error)
}
}
}
} }
.headerProminence(.increased) .headerProminence(.increased)
.onAppear { .onAppear {

@ -40,115 +40,120 @@ struct BroadcastView: View {
.tipStyle(tint: nil) .tipStyle(tint: nil)
} }
Section { if tournament.isPrivate == false {
LabeledContent {
if tournament.areTeamsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green)
} else {
Text(tournament.publishedTeamsDate().formatted())
}
} label: {
if tournament.areTeamsPublished() {
Text("Publiée")
} else {
Text("Publication prévue")
}
}
Text("Les horaires de convocations ne seront pas publiés").foregroundStyle(.secondary)
} header: {
Text("Liste des équipes")
} footer: {
if Date() < tournament.publishedTeamsDate() {
HStack {
Spacer()
FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") {
tournament.publishTeams.toggle()
}
}
}
}
Section {
LabeledContent {
if tournament.areSummonsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green)
} else {
Text(tournament.publishedTeamsDate().formatted())
}
} label: {
if tournament.areSummonsPublished() {
Text("Publiées")
} else {
Text("Publication prévue")
}
}
} header: {
Text("Convocations")
} footer: {
if Date() < tournament.publishedTeamsDate() {
HStack {
Spacer()
FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") {
tournament.publishSummons.toggle()
}
}
}
}
if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() {
Section { Section {
let areGroupStagesPublished = tournament.areGroupStagesPublished()
LabeledContent { LabeledContent {
if areGroupStagesPublished { if tournament.areTeamsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green) Image(systemName:"checkmark").foregroundStyle(.green)
} else { } else {
Text(publishedGroupStagesDate.formatted()) Text(tournament.publishedTeamsDate().formatted())
} }
} label: { } label: {
if areGroupStagesPublished { if tournament.areTeamsPublished() {
Text("Publiées") Text("Publiée")
} else { } else {
Text("Publication prévue") Text("Publication prévue")
} }
} }
Toggle(isOn: $tournament.hideTeamsWeight) {
Text("Masquer les poids des équipes")
}
} header: { } header: {
Text("Poules") Text("Liste des équipes")
} footer: { } footer: {
if Date() < publishedGroupStagesDate { if Date() < tournament.publishedTeamsDate() {
HStack { HStack {
Spacer() Spacer()
FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") { FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") {
tournament.publishGroupStages.toggle() tournament.publishTeams.toggle()
} }
} }
} }
} }
}
if let publishedBracketsDate = tournament.publishedBracketsDate() {
Section { Section {
let areBracketsPublished = tournament.areBracketsPublished()
LabeledContent { LabeledContent {
if areBracketsPublished { if tournament.areSummonsPublished() {
Image(systemName:"checkmark").foregroundStyle(.green) Image(systemName:"checkmark").foregroundStyle(.green)
} else { } else {
Text(publishedBracketsDate.formatted()) Text(tournament.publishedTeamsDate().formatted())
} }
} label: { } label: {
if areBracketsPublished { if tournament.areSummonsPublished() {
Text("Publié") Text("Publiées")
} else { } else {
Text("Publication prévue") Text("Publication prévue")
} }
} }
} header: { } header: {
Text("Tableau") Text("Convocations")
} footer: { } footer: {
if Date() < publishedBracketsDate { if Date() < tournament.publishedTeamsDate() {
HStack { HStack {
Spacer() Spacer()
FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") { FooterButtonView(tournament.publishSummons ? "masquer sur le site" : "publier maintenant") {
tournament.publishBrackets.toggle() tournament.publishSummons.toggle()
}
}
}
}
if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() {
Section {
let areGroupStagesPublished = tournament.areGroupStagesPublished()
LabeledContent {
if areGroupStagesPublished {
Image(systemName:"checkmark").foregroundStyle(.green)
} else {
Text(publishedGroupStagesDate.formatted())
}
} label: {
if areGroupStagesPublished {
Text("Publiées")
} else {
Text("Publication prévue")
}
}
} header: {
Text("Poules")
} footer: {
if Date() < publishedGroupStagesDate {
HStack {
Spacer()
FooterButtonView(tournament.publishGroupStages ? "masquer sur le site" : "publier maintenant") {
tournament.publishGroupStages.toggle()
}
}
}
}
}
if let publishedBracketsDate = tournament.publishedBracketsDate() {
Section {
let areBracketsPublished = tournament.areBracketsPublished()
LabeledContent {
if areBracketsPublished {
Image(systemName:"checkmark").foregroundStyle(.green)
} else {
Text(publishedBracketsDate.formatted())
}
} label: {
if areBracketsPublished {
Text("Publié")
} else {
Text("Publication prévue")
}
}
} header: {
Text("Tableau")
} footer: {
if Date() < publishedBracketsDate {
HStack {
Spacer()
FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") {
tournament.publishBrackets.toggle()
}
} }
} }
} }
@ -228,7 +233,7 @@ struct BroadcastView: View {
UIPasteboard.general.string = urlToShow UIPasteboard.general.string = urlToShow
} }
} }
.onChange(of: [tournament.isPrivate, tournament.publishTeams, tournament.publishSummons, tournament.publishBrackets, tournament.publishGroupStages]) { .onChange(of: [tournament.hideTeamsWeight, tournament.isPrivate, tournament.publishTeams, tournament.publishSummons, tournament.publishBrackets, tournament.publishGroupStages]) {
_save() _save()
} }
} }

@ -52,6 +52,13 @@ struct TournamentClubSettingsView: View {
Section { Section {
TournamentFieldsManagerView(localizedStringKey: "Terrains pour le tournoi", count: $tournament.courtCount) TournamentFieldsManagerView(localizedStringKey: "Terrains pour le tournoi", count: $tournament.courtCount)
.onChange(of: tournament.courtCount) {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
if let event = tournament.eventObject() { if let event = tournament.eventObject() {
NavigationLink { NavigationLink {

@ -135,8 +135,9 @@ struct InscriptionManagerView: View {
} }
} }
.onAppear { .onAppear {
if self.teamsHash == nil { let selectedSortedTeams = tournament.selectedSortedTeams()
self.teamsHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) if self.teamsHash == nil, selectedSortedTeams.isEmpty == false {
self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
} }
} }
.onDisappear { .onDisappear {
@ -424,6 +425,11 @@ struct InscriptionManagerView: View {
let teamIndex = team.index(in: sortedTeams) let teamIndex = team.index(in: sortedTeams)
Section { Section {
TeamDetailView(team: team) TeamDetailView(team: team)
.contextMenu {
Button("équipe présente") {
team.hasArrived()
}
}
} header: { } header: {
TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament) TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament)
} footer: { } footer: {
@ -606,7 +612,6 @@ struct InscriptionManagerView: View {
private func _informationView(count: Int) -> some View { private func _informationView(count: Int) -> some View {
Section { Section {
let unsortedTeams = tournament.unsortedTeams()
let walkoutTeams = tournament.walkoutTeams() let walkoutTeams = tournament.walkoutTeams()
let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()

@ -72,10 +72,7 @@ struct PrintSettingsView: View {
} label: { } label: {
Text("Aperçu du tableau") Text("Aperçu du tableau")
} }
} ForEach(tournament.groupStages()) { groupStage in
ForEach(tournament.groupStages()) { groupStage in
Section {
NavigationLink { NavigationLink {
WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in
if let error { if let error {

@ -78,9 +78,10 @@ struct TournamentCashierView: View {
let tournamentHasEnded = tournament.hasEnded() let tournamentHasEnded = tournament.hasEnded()
if tournamentHasEnded { if tournamentHasEnded {
allDestinations.append(.summary) allDestinations.append(.summary)
allDestinations.append(.all(tournament))
} }
allDestinations.append(.all(tournament))
let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) }
allDestinations.append(contentsOf: destinations) allDestinations.append(contentsOf: destinations)
tournament.rounds().forEach { round in tournament.rounds().forEach { round in
@ -90,7 +91,6 @@ struct TournamentCashierView: View {
} }
if tournamentHasEnded == false { if tournamentHasEnded == false {
allDestinations.append(.all(tournament))
allDestinations.append(.summary) allDestinations.append(.summary)
} }
@ -99,11 +99,19 @@ struct TournamentCashierView: View {
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
let gs = tournament.getActiveGroupStage() if tournament.hasEnded() {
if let gs { if tournament.players().anySatisfy({ $0.hasPaid() == false }) == false {
_selectedDestination = State(wrappedValue: .groupStage(gs)) _selectedDestination = .init(wrappedValue: .summary)
} else if let rs = tournament.getActiveRound(withSeeds: true) { } else {
_selectedDestination = State(wrappedValue: .bracket(rs)) _selectedDestination = .init(wrappedValue: .all(tournament))
}
} else {
let gs = tournament.getActiveGroupStage()
if let gs {
_selectedDestination = State(wrappedValue: .groupStage(gs))
} else if let rs = tournament.getActiveRound(withSeeds: true) {
_selectedDestination = State(wrappedValue: .bracket(rs))
}
} }
} }

@ -52,8 +52,14 @@ struct TournamentCellView: View {
// .frame(width: 2) // .frame(width: 2)
VStack(alignment: .leading, spacing: 0.0) { VStack(alignment: .leading, spacing: 0.0) {
if let tournament = tournament as? Tournament { if let tournament = tournament as? Tournament {
Text(tournament.locationLabel(displayStyle)) HStack {
.font(.caption) Text(tournament.locationLabel(displayStyle))
.font(.caption)
Spacer()
if tournament.isPrivate {
Text("privé").foregroundStyle(.secondary)
}
}
} else { } else {
Text(tournament.clubLabel()) Text(tournament.clubLabel())
.font(.caption) .font(.caption)

@ -43,8 +43,11 @@ struct TournamentInitView: View {
NavigationLink(value: Screen.broadcast) { NavigationLink(value: Screen.broadcast) {
LabeledContent { LabeledContent {
Image(systemName: tournament.isPrivate ? "tv.slash" : "checkmark") if tournament.isPrivate {
.foregroundStyle(tournament.isPrivate ? .logoRed : .green) Text("tournoi privé").foregroundStyle(.logoRed)
} else {
Text("Automatique")
}
} label: { } label: {
Text("Publication") Text("Publication")
} }

@ -96,7 +96,7 @@ final class ServerDataTests: XCTestCase {
return 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, shouldVerifyBracket: true, shouldVerifyGroupStage: 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, hideTeamsWeight: false)
let t = try await Store.main.service().post(tournament) let t = try await Store.main.service().post(tournament)
assert(t.event == tournament.event) assert(t.event == tournament.event)
@ -132,6 +132,7 @@ final class ServerDataTests: XCTestCase {
assert(t.publishBrackets == tournament.publishBrackets) assert(t.publishBrackets == tournament.publishBrackets)
assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket) assert(t.shouldVerifyBracket == tournament.shouldVerifyBracket)
assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage) assert(t.shouldVerifyGroupStage == tournament.shouldVerifyGroupStage)
assert(t.hideTeamsWeight == tournament.hideTeamsWeight)
} }
func testGroupStage() async throws { func testGroupStage() async throws {
@ -254,7 +255,7 @@ final class ServerDataTests: XCTestCase {
let rounds: [Round] = try await Store.main.service().get() let rounds: [Round] = try await Store.main.service().get()
let parentRoundId = rounds.first?.id let parentRoundId = rounds.first?.id
let match: Match = Match(round: parentRoundId, groupStage: nil, startDate: Date(), endDate: Date(), index: 2, matchFormat: MatchFormat.twoSets, servingTeamId: teamRegistrationId, winningTeamId: teamRegistrationId, losingTeamId: teamRegistrationId, disabled: true, courtIndex: 1) let match: Match = Match(round: parentRoundId, groupStage: nil, startDate: Date(), endDate: Date(), index: 2, matchFormat: MatchFormat.twoSets, servingTeamId: teamRegistrationId, winningTeamId: teamRegistrationId, losingTeamId: teamRegistrationId, disabled: true, courtIndex: 1, confirmed: true)
let m: Match = try await Store.main.service().post(match) let m: Match = try await Store.main.service().post(match)
assert(m.round == match.round) assert(m.round == match.round)
@ -268,6 +269,7 @@ final class ServerDataTests: XCTestCase {
assert(m.losingTeamId == match.losingTeamId) assert(m.losingTeamId == match.losingTeamId)
assert(m.disabled == match.disabled) assert(m.disabled == match.disabled)
assert(m.courtIndex == match.courtIndex) assert(m.courtIndex == match.courtIndex)
assert(m.confirmed == match.confirmed)
} }
@ -283,15 +285,11 @@ final class ServerDataTests: XCTestCase {
assertionFailure("missing teamRegistrations in database") assertionFailure("missing teamRegistrations in database")
return return
} }
let playerRegistrations: [PlayerRegistration] = try await Store.main.service().get() let teamScore = TeamScore(match: matchId, teamRegistration: teamRegistrationId, score: "6/6", walkOut: 1, luckyLoser: 1)
let regs = playerRegistrations.prefix(upTo: 2).map { $0.id }
let teamScore = TeamScore(match: matchId, teamRegistration: teamRegistrationId, playerRegistrations: regs, score: "6/6", walkOut: 1, luckyLoser: 1)
let ts: TeamScore = try await Store.main.service().post(teamScore) let ts: TeamScore = try await Store.main.service().post(teamScore)
assert(ts.match == teamScore.match) assert(ts.match == teamScore.match)
assert(ts.teamRegistration == teamScore.teamRegistration) assert(ts.teamRegistration == teamScore.teamRegistration)
assert(ts.playerRegistrations == teamScore.playerRegistrations)
assert(ts.score == teamScore.score) assert(ts.score == teamScore.score)
assert(ts.walkOut == teamScore.walkOut) assert(ts.walkOut == teamScore.walkOut)
assert(ts.luckyLoser == teamScore.luckyLoser) assert(ts.luckyLoser == teamScore.luckyLoser)

Loading…
Cancel
Save