Laurent 2 years ago
commit dd7b6c8b34
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/GroupStage.swift
  3. 7
      PadelClub/Data/Match.swift
  4. 38
      PadelClub/Data/MatchScheduler.swift
  5. 6
      PadelClub/Data/Round.swift
  6. 83
      PadelClub/Data/Tournament.swift
  7. 2
      PadelClub/Utils/URLs.swift
  8. 17
      PadelClub/ViewModel/NavigationViewModel.swift
  9. 2
      PadelClub/ViewModel/SearchViewModel.swift
  10. 42
      PadelClub/Views/Club/ClubDetailView.swift
  11. 1
      PadelClub/Views/Club/CourtView.swift
  12. 2
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  13. 16
      PadelClub/Views/Navigation/Organizer/TournamentButtonView.swift
  14. 14
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  15. 37
      PadelClub/Views/Planning/PlanningSettingsView.swift
  16. 2
      PadelClub/Views/Round/RoundSettingsView.swift
  17. 26
      PadelClub/Views/Round/RoundView.swift
  18. 136
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  19. 7
      PadelClubTests/ServerDataTests.swift

@ -1830,7 +1830,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 = 6; CURRENT_PROJECT_VERSION = 7;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1868,7 +1868,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 = 6; CURRENT_PROJECT_VERSION = 7;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -79,7 +79,7 @@ class GroupStage: ModelObject, Storable {
var _matches = [Match]() var _matches = [Match]()
for i in 0..<_numberOfMatchesToBuild() { for i in 0..<_numberOfMatchesToBuild() {
let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat) let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat, name: localizedMatchUpLabel(for: i))
_matches.append(newMatch) _matches.append(newMatch)
} }

@ -13,6 +13,11 @@ class Match: ModelObject, Storable {
static func resourceName() -> String { "matches" } static func resourceName() -> String { "matches" }
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static func setServerTitle(upperRound: Round, matchIndex: Int) -> String {
if upperRound.index == 0 { return upperRound.roundTitle() }
return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted()
}
var byeState: Bool = false var byeState: Bool = false
var id: String = Store.randomId() var id: String = Store.randomId()
@ -27,7 +32,7 @@ class Match: ModelObject, Storable {
var winningTeamId: String? var winningTeamId: String?
var losingTeamId: String? var losingTeamId: String?
//var broadcasted: Bool //var broadcasted: Bool
private var name: String? var name: String?
//var order: Int //var order: Int
var disabled: Bool = false var disabled: Bool = false
private(set) var courtIndex: Int? private(set) var courtIndex: Int?

@ -26,6 +26,7 @@ class MatchScheduler : ModelObject, Storable {
var rotationDifferenceIsImportant: Bool var rotationDifferenceIsImportant: Bool
var shouldHandleUpperRoundSlice: Bool var shouldHandleUpperRoundSlice: Bool
var shouldEndRoundBeforeStartingNext: Bool var shouldEndRoundBeforeStartingNext: Bool
var groupStageChunkCount: Int?
init(tournament: String, init(tournament: String,
timeDifferenceLimit: Int = 5, timeDifferenceLimit: Int = 5,
@ -36,7 +37,8 @@ class MatchScheduler : ModelObject, Storable {
randomizeCourts: Bool = true, randomizeCourts: Bool = true,
rotationDifferenceIsImportant: Bool = false, rotationDifferenceIsImportant: Bool = false,
shouldHandleUpperRoundSlice: Bool = true, shouldHandleUpperRoundSlice: Bool = true,
shouldEndRoundBeforeStartingNext: Bool = true) { shouldEndRoundBeforeStartingNext: Bool = true,
groupStageChunkCount: Int? = nil) {
self.tournament = tournament self.tournament = tournament
self.timeDifferenceLimit = timeDifferenceLimit self.timeDifferenceLimit = timeDifferenceLimit
self.loserBracketRotationDifference = loserBracketRotationDifference self.loserBracketRotationDifference = loserBracketRotationDifference
@ -47,6 +49,7 @@ class MatchScheduler : ModelObject, Storable {
self.rotationDifferenceIsImportant = rotationDifferenceIsImportant self.rotationDifferenceIsImportant = rotationDifferenceIsImportant
self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice
self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext
self.groupStageChunkCount = groupStageChunkCount
} }
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
@ -61,6 +64,7 @@ class MatchScheduler : ModelObject, Storable {
case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant"
case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice"
case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext"
case _groupStageChunkCount = "groupStageChunkCount"
} }
var courtsUnavailability: [DateInterval]? { var courtsUnavailability: [DateInterval]? {
@ -77,7 +81,7 @@ class MatchScheduler : ModelObject, Storable {
@discardableResult @discardableResult
func updateGroupStageSchedule(tournament: Tournament) -> Date { func updateGroupStageSchedule(tournament: Tournament) -> Date {
let groupStageCourtCount = tournament.groupStageCourtCount ?? 1 let computedGroupStageChunkCount = groupStageChunkCount ?? 1
let groupStages = tournament.groupStages() let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
@ -88,7 +92,35 @@ class MatchScheduler : ModelObject, Storable {
}) })
var lastDate : Date = tournament.startDate var lastDate : Date = tournament.startDate
groupStages.chunked(into: groupStageCourtCount).forEach { groups in let times = Set(groupStages.compactMap { $0.startDate }).sorted()
if let first = times.first {
if first.isEarlierThan(tournament.startDate) {
tournament.startDate = first
try? DataStore.shared.tournaments.addOrUpdate(instance: tournament)
}
}
times.forEach({ time in
lastDate = time
let groups = groupStages.filter({ $0.startDate == time })
let dispatch = groupStageDispatcher(numberOfCourtsAvailablePerRotation: numberOfCourtsAvailablePerRotation, groupStages: groups, startingDate: lastDate)
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60)
}
match.setCourt(matchSchedule.courtIndex)
}
}
})
groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: computedGroupStageChunkCount).forEach { groups in
groups.forEach({ $0.startDate = lastDate }) groups.forEach({ $0.startDate = lastDate })
try? DataStore.shared.groupStages.addOrUpdate(contentOfs: groups) try? DataStore.shared.groupStages.addOrUpdate(contentOfs: groups)

@ -431,13 +431,9 @@ class Round: ModelObject, Storable {
let matches = (0..<matchCount).map { //0 is final match let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0) let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex] let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat) return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat, name: round.roundTitle())
} }
print(matches.map {
(RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
})
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches) try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
loserRounds().forEach { round in loserRounds().forEach { round in

@ -32,7 +32,6 @@ class Tournament : ModelObject, Storable {
var federalCategory: TournamentCategory var federalCategory: TournamentCategory
var federalLevelCategory: TournamentLevel var federalLevelCategory: TournamentLevel
var federalAgeCategory: FederalTournamentAge var federalAgeCategory: FederalTournamentAge
var groupStageCourtCount: Int?
var closedRegistrationDate: Date? var closedRegistrationDate: Date?
var groupStageAdditionalQualified: Int var groupStageAdditionalQualified: Int
var courtCount: Int = 2 var courtCount: Int = 2
@ -44,9 +43,8 @@ class Tournament : ModelObject, Storable {
var additionalEstimationDuration: Int = 0 var additionalEstimationDuration: Int = 0
var isDeleted: Bool = false var isDeleted: Bool = false
var isCanceled: Bool = false var isCanceled: Bool = false
var publishManually: Bool = false
var publishTeams: Bool = false var publishTeams: Bool = false
var publishWaitingList: Bool = false //var publishWaitingList: Bool = false
var publishSummons: Bool = false var publishSummons: Bool = false
var publishGroupStages: Bool = false var publishGroupStages: Bool = false
var publishBrackets: Bool = false var publishBrackets: Bool = false
@ -75,7 +73,6 @@ class Tournament : ModelObject, Storable {
case _federalCategory = "federalCategory" case _federalCategory = "federalCategory"
case _federalLevelCategory = "federalLevelCategory" case _federalLevelCategory = "federalLevelCategory"
case _federalAgeCategory = "federalAgeCategory" case _federalAgeCategory = "federalAgeCategory"
case _groupStageCourtCount = "groupStageCourtCount"
case _seedCount = "seedCount" case _seedCount = "seedCount"
case _closedRegistrationDate = "closedRegistrationDate" case _closedRegistrationDate = "closedRegistrationDate"
case _groupStageAdditionalQualified = "groupStageAdditionalQualified" case _groupStageAdditionalQualified = "groupStageAdditionalQualified"
@ -88,15 +85,14 @@ class Tournament : ModelObject, Storable {
case _isDeleted = "isDeleted" case _isDeleted = "isDeleted"
case _isCanceled = "localId" case _isCanceled = "localId"
case _payment = "globalId" case _payment = "globalId"
case _publishManually = "publishManually"
case _publishTeams = "publishTeams" case _publishTeams = "publishTeams"
case _publishWaitingList = "publishWaitingList" //case _publishWaitingList = "publishWaitingList"
case _publishSummons = "publishSummons" case _publishSummons = "publishSummons"
case _publishGroupStages = "publishGroupStages" case _publishGroupStages = "publishGroupStages"
case _publishBrackets = "publishBrackets" case _publishBrackets = "publishBrackets"
} }
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, groupStageCourtCount: Int? = nil, 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, publishManually: Bool = false, publishTeams: Bool = false, publishWaitingList: 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) {
self.event = event self.event = event
self.name = name self.name = name
self.startDate = startDate self.startDate = startDate
@ -115,7 +111,6 @@ class Tournament : ModelObject, Storable {
self.federalCategory = federalCategory self.federalCategory = federalCategory
self.federalLevelCategory = federalLevelCategory self.federalLevelCategory = federalLevelCategory
self.federalAgeCategory = federalAgeCategory self.federalAgeCategory = federalAgeCategory
self.groupStageCourtCount = groupStageCourtCount
self.closedRegistrationDate = closedRegistrationDate self.closedRegistrationDate = closedRegistrationDate
self.groupStageAdditionalQualified = groupStageAdditionalQualified self.groupStageAdditionalQualified = groupStageAdditionalQualified
self.courtCount = courtCount self.courtCount = courtCount
@ -125,6 +120,10 @@ class Tournament : ModelObject, Storable {
self.entryFee = entryFee self.entryFee = entryFee
self.additionalEstimationDuration = additionalEstimationDuration self.additionalEstimationDuration = additionalEstimationDuration
self.isDeleted = isDeleted self.isDeleted = isDeleted
self.publishTeams = publishTeams
self.publishSummons = publishSummons
self.publishBrackets = publishBrackets
self.publishGroupStages = publishGroupStages
} }
required init(from decoder: Decoder) throws { required init(from decoder: Decoder) throws {
@ -148,7 +147,6 @@ class Tournament : ModelObject, Storable {
federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory) federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory)
federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory) federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory)
federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory) federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory)
groupStageCourtCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCourtCount)
closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate)
groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified) groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified)
courtCount = try container.decode(Int.self, forKey: ._courtCount) courtCount = try container.decode(Int.self, forKey: ._courtCount)
@ -160,9 +158,7 @@ class Tournament : ModelObject, Storable {
additionalEstimationDuration = try container.decode(Int.self, forKey: ._additionalEstimationDuration) additionalEstimationDuration = try container.decode(Int.self, forKey: ._additionalEstimationDuration)
isDeleted = try container.decode(Bool.self, forKey: ._isDeleted) isDeleted = try container.decode(Bool.self, forKey: ._isDeleted)
isCanceled = try Tournament._decodeCanceled(container: container) isCanceled = try Tournament._decodeCanceled(container: container)
publishManually = try container.decodeIfPresent(Bool.self, forKey: ._publishManually) ?? false
publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false
publishWaitingList = try container.decodeIfPresent(Bool.self, forKey: ._publishWaitingList) ?? false
publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false
publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false
publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false
@ -222,7 +218,6 @@ class Tournament : ModelObject, Storable {
try container.encode(federalCategory, forKey: ._federalCategory) try container.encode(federalCategory, forKey: ._federalCategory)
try container.encode(federalLevelCategory, forKey: ._federalLevelCategory) try container.encode(federalLevelCategory, forKey: ._federalLevelCategory)
try container.encode(federalAgeCategory, forKey: ._federalAgeCategory) try container.encode(federalAgeCategory, forKey: ._federalAgeCategory)
try container.encodeIfPresent(groupStageCourtCount, forKey: ._groupStageCourtCount)
try container.encodeIfPresent(closedRegistrationDate, forKey: ._closedRegistrationDate) try container.encodeIfPresent(closedRegistrationDate, forKey: ._closedRegistrationDate)
try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified)
try container.encode(courtCount, forKey: ._courtCount) try container.encode(courtCount, forKey: ._courtCount)
@ -234,7 +229,10 @@ class Tournament : ModelObject, Storable {
try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration) try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration)
try container.encode(isDeleted, forKey: ._isDeleted) try container.encode(isDeleted, forKey: ._isDeleted)
try self._encodeIsCanceled(container: &container) try self._encodeIsCanceled(container: &container)
try container.encode(publishTeams, forKey: ._publishTeams)
try container.encode(publishSummons, forKey: ._publishSummons)
try container.encode(publishBrackets, forKey: ._publishBrackets)
try container.encode(publishGroupStages, forKey: ._publishGroupStages)
} }
fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws { fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws {
@ -295,6 +293,61 @@ class Tournament : ModelObject, Storable {
case canceled case canceled
} }
func publishedTeamsDate() -> Date {
startDate
}
func areTeamsPublished() -> Bool {
Date() >= startDate || publishTeams
}
func areSummonsPublished() -> Bool {
Date() >= startDate || publishSummons
}
func publishedGroupStagesDate() -> Date? {
if let first = groupStages().flatMap({ $0.playedMatches() }).compactMap({ $0.startDate }).sorted().first?.atNine() {
if first.isEarlierThan(startDate) {
return startDate
} else {
return first
}
} else {
return nil
}
}
func areGroupStagesPublished() -> Bool {
if publishGroupStages { return true }
if let publishedGroupStagesDate = publishedGroupStagesDate() {
return Date() >= publishedGroupStagesDate
} else {
return false
}
}
func publishedBracketsDate() -> Date? {
if let first = rounds().flatMap({ $0.playedMatches() }).compactMap({ $0.startDate }).sorted().first?.atNine() {
if first.isEarlierThan(startDate) {
return startDate
} else {
return first
}
} else {
return nil
}
}
func areBracketsPublished() -> Bool {
if publishBrackets { return true }
if let publishedBracketsDate = publishedBracketsDate() {
return Date() >= publishedBracketsDate
} else {
return false
}
}
func shareURL() -> URL? { func shareURL() -> URL? {
return URLs.main.url.appending(path: "tournament/\(id)") return URLs.main.url.appending(path: "tournament/\(id)")
} }
@ -1096,7 +1149,7 @@ class Tournament : ModelObject, Storable {
let matches = (0..<matchCount).map { //0 is final match let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0) let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex] let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: matchFormat) return Match(round: round.id, index: $0, matchFormat: matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
} }
print(matches.map { print(matches.map {
@ -1579,7 +1632,7 @@ extension Tournament {
} }
static func fake() -> Tournament { static func fake() -> Tournament {
return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
} }
} }

@ -11,7 +11,7 @@ enum URLs: String, Identifiable {
case subscriptions = "https://apple.co/2Th4vqI" case subscriptions = "https://apple.co/2Th4vqI"
case main = "https://xlr.alwaysdata.net/" case main = "https://xlr.alwaysdata.net/"
case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/"
case padelClub = "https://padelclub.app" //case padelClub = "https://padelclub.app"
var id: String { return self.rawValue } var id: String { return self.rawValue }

@ -16,4 +16,21 @@ class NavigationViewModel {
var selectedTab: TabDestination? var selectedTab: TabDestination?
var agendaDestination: AgendaDestination? = .activity var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament? var tournament: Tournament?
var organizerTournament: Tournament?
func isTournamentAlreadyOpenInOrganizer(_ tournament: Tournament) -> Bool {
organizerTournament?.id == tournament.id
}
func closeTournamentFromOrganizer(_ tournament: Tournament) {
tournament.navigationPath.removeAll()
organizerTournament = nil
}
func openTournamentInOrganizer(_ tournament: Tournament) {
organizerTournament = tournament
if selectedTab != .tournamentOrganizer {
selectedTab = .tournamentOrganizer
}
}
} }

@ -69,7 +69,7 @@ class SearchViewModel: ObservableObject, Identifiable {
} }
func codeClubs() -> [String] { func codeClubs() -> [String] {
DataStore.shared.clubs.compactMap { $0.code } DataStore.shared.user.clubsObjects().compactMap { $0.code }
} }
func getCodeClub() -> String? { func getCodeClub() -> String? {

@ -27,21 +27,37 @@ struct ClubDetailView: View {
var body: some View { var body: some View {
Form { Form {
Section { Section {
VStack(alignment: .leading, spacing: 0) { NavigationLink {
Text("Nom du club").foregroundStyle(.secondary).font(.caption) ClubSearchView(displayContext: .edition, club: club)
} label: {
Label("Chercher dans la base fédérale", systemImage: "magnifyingglass")
}
} footer: {
Text("Vous pouvez chercher un club dans la base fédérale et importer les informations directement.")
}
Section {
LabeledContent {
TextField("Nom du club", text: $club.name) TextField("Nom du club", text: $club.name)
.fixedSize() .autocorrectionDisabled()
.keyboardType(.alphabet)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
.focused($focusedField, equals: ._name) .focused($focusedField, equals: ._name)
.submitLabel( displayContext == .addition ? .next : .done) .submitLabel( displayContext == .addition ? .next : .done)
.onSubmit { .onSubmit {
if club.acronym.isEmpty { if club.acronym.isEmpty {
club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
focusedField = ._city
} }
if displayContext == .addition { if displayContext == .addition {
focusedField = ._acronym focusedField = ._acronym
} }
} }
} label: {
Text("Nom du club")
} }
.onTapGesture { .onTapGesture {
focusedField = ._name focusedField = ._name
@ -92,10 +108,12 @@ struct ClubDetailView: View {
} }
if club.code == nil { if club.code == nil {
VStack(alignment: .leading, spacing: 0) { LabeledContent {
Text("Ville").foregroundStyle(.secondary).font(.caption)
TextField("Ville", text: $city) TextField("Ville", text: $city)
.fixedSize() .autocorrectionDisabled()
.keyboardType(.alphabet)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
.focused($focusedField, equals: ._city) .focused($focusedField, equals: ._city)
.submitLabel( displayContext == .addition ? .next : .done) .submitLabel( displayContext == .addition ? .next : .done)
.onSubmit { .onSubmit {
@ -104,20 +122,26 @@ struct ClubDetailView: View {
} }
club.city = city club.city = city
} }
} label: {
Text("Ville")
} }
.onTapGesture { .onTapGesture {
focusedField = ._city focusedField = ._city
} }
VStack(alignment: .leading, spacing: 0) { LabeledContent {
Text("Code Postal").foregroundStyle(.secondary).font(.caption)
TextField("Code Postal", text: $zipCode) TextField("Code Postal", text: $zipCode)
.fixedSize() .autocorrectionDisabled()
.keyboardType(.alphabet)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
.focused($focusedField, equals: ._zipCode) .focused($focusedField, equals: ._zipCode)
.submitLabel( displayContext == .addition ? .next : .done) .submitLabel( displayContext == .addition ? .next : .done)
.onSubmit { .onSubmit {
club.zipCode = zipCode club.zipCode = zipCode
} }
} label: {
Text("Code Postal")
} }
.onTapGesture { .onTapGesture {
focusedField = ._zipCode focusedField = ._zipCode

@ -22,6 +22,7 @@ struct CourtView: View {
Section { Section {
LabeledContent { LabeledContent {
TextField("Nom", text: $name) TextField("Nom", text: $name)
.autocorrectionDisabled()
.keyboardType(.alphabet) .keyboardType(.alphabet)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

@ -103,7 +103,7 @@ struct EventListView: View {
} }
.contextMenu { .contextMenu {
Button { Button {
navigation.openTournamentInOrganizer(tournament)
} label: { } label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow") Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
} }

@ -8,21 +8,15 @@
import SwiftUI import SwiftUI
struct TournamentButtonView: View { struct TournamentButtonView: View {
@Environment(NavigationViewModel.self) private var navigation
let tournament: Tournament let tournament: Tournament
@Binding var selectedId: String?
var body: some View { var body: some View {
Button { Button {
if selectedId == tournament.id { if navigation.isTournamentAlreadyOpenInOrganizer(tournament) {
tournament.navigationPath.removeAll() navigation.closeTournamentFromOrganizer(tournament)
selectedId = nil
// if tournament.navigationPath.isEmpty {
// selectedId = nil
// } else {
// tournament.navigationPath.removeLast()
// }
} else { } else {
selectedId = tournament.id navigation.openTournamentInOrganizer(tournament)
} }
} label: { } label: {
TournamentCellView(tournament: tournament, displayStyle: .short) TournamentCellView(tournament: tournament, displayStyle: .short)
@ -34,7 +28,7 @@ struct TournamentButtonView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
.overlay(alignment: .top) { .overlay(alignment: .top) {
if selectedId == tournament.id { if navigation.isTournamentAlreadyOpenInOrganizer(tournament) {
Image(systemName: "ellipsis") Image(systemName: "ellipsis")
.offset(y: -10) .offset(y: -10)
} }

@ -10,17 +10,13 @@ import LeStorage
struct TournamentOrganizerView: View { struct TournamentOrganizerView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State private var selectedTournamentId: String? @Environment(NavigationViewModel.self) private var navigation
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(dataStore.tournaments) { tournament in if let tournament = navigation.organizerTournament {
if tournament.id == selectedTournamentId {
OrganizedTournamentView(tournament: tournament) OrganizedTournamentView(tournament: tournament)
} } else {
}
if selectedTournamentId == nil {
NavigationStack { NavigationStack {
let userClubsEmpty = dataStore.user.clubs.isEmpty let userClubsEmpty = dataStore.user.clubs.isEmpty
ContentUnavailableView( ContentUnavailableView(
@ -39,7 +35,7 @@ struct TournamentOrganizerView: View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack { HStack {
ForEach(dataStore.tournaments) { tournament in ForEach(dataStore.tournaments) { tournament in
TournamentButtonView(tournament: tournament, selectedId: $selectedTournamentId) TournamentButtonView(tournament: tournament)
} }
} }
.padding() .padding()
@ -48,7 +44,7 @@ struct TournamentOrganizerView: View {
} }
} }
.onChange(of: Store.main.currentUserUUID) { .onChange(of: Store.main.currentUserUUID) {
selectedTournamentId = nil navigation.organizerTournament = nil
} }
} }
} }

@ -13,7 +13,7 @@ struct PlanningSettingsView: View {
@Bindable var tournament: Tournament @Bindable var tournament: Tournament
@Bindable var matchScheduler: MatchScheduler @Bindable var matchScheduler: MatchScheduler
@State private var groupStageCourtCount: Int @State private var groupStageChunkCount: Int
@State private var isScheduling: Bool = false @State private var isScheduling: Bool = false
@State private var schedulingDone: Bool = false @State private var schedulingDone: Bool = false
@State private var showOptions: Bool = false @State private var showOptions: Bool = false
@ -22,10 +22,11 @@ struct PlanningSettingsView: View {
self.tournament = tournament self.tournament = tournament
if let matchScheduler = tournament.matchScheduler() { if let matchScheduler = tournament.matchScheduler() {
self.matchScheduler = matchScheduler self.matchScheduler = matchScheduler
self._groupStageChunkCount = State(wrappedValue: matchScheduler.groupStageChunkCount ?? 1)
} else { } else {
self.matchScheduler = MatchScheduler(tournament: tournament.id) self.matchScheduler = MatchScheduler(tournament: tournament.id)
self._groupStageChunkCount = State(wrappedValue: 1)
} }
self._groupStageCourtCount = State(wrappedValue: tournament.groupStageCourtCount ?? 1)
} }
var body: some View { var body: some View {
@ -48,7 +49,7 @@ struct PlanningSettingsView: View {
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount)
if tournament.groupStages().isEmpty == false { if tournament.groupStages().isEmpty == false {
TournamentFieldsManagerView(localizedStringKey: "Nombre de poule en même temps", count: $groupStageCourtCount, max: tournament.groupStageCount) TournamentFieldsManagerView(localizedStringKey: "Nombre de poule en même temps", count: $groupStageChunkCount, max: tournament.groupStageCount)
} }
if let event = tournament.eventObject() { if let event = tournament.eventObject() {
@ -59,26 +60,25 @@ struct PlanningSettingsView: View {
Text("Préciser la disponibilité des terrains") Text("Préciser la disponibilité des terrains")
} }
} }
} footer: {
FooterButtonView((showOptions ? "masquer" : "voir") + " les réglages avancées") {
showOptions.toggle()
}
}
if showOptions {
_optionsView()
} }
Section { Section {
RowButtonView("Horaire intelligent", role: .destructive) { RowButtonView("Horaire intelligent", role: .destructive) {
schedulingDone = false schedulingDone = false
await _setupSchedule() await _setupSchedule()
_save()
schedulingDone = true schedulingDone = true
} }
} footer: { } footer: {
Button { 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()
showOptions.toggle()
} label: {
Text((showOptions ? "masquer" : "voir") + " les réglages avancées")
.underline()
}
.buttonStyle(.borderless)
}
if showOptions {
_optionsView()
} }
Section { Section {
@ -101,6 +101,7 @@ struct PlanningSettingsView: View {
} }
} }
} }
.headerProminence(.increased)
.onAppear { .onAppear {
do { do {
try dataStore.matchSchedulers.addOrUpdate(instance: matchScheduler) try dataStore.matchSchedulers.addOrUpdate(instance: matchScheduler)
@ -115,9 +116,8 @@ struct PlanningSettingsView: View {
.deferredRendering(for: .seconds(2)) .deferredRendering(for: .seconds(2))
} }
} }
.onChange(of: groupStageCourtCount) { .onChange(of: groupStageChunkCount) {
tournament.groupStageCourtCount = groupStageCourtCount matchScheduler.groupStageChunkCount = groupStageChunkCount
_save()
} }
.onChange(of: tournament.startDate) { .onChange(of: tournament.startDate) {
_save() _save()
@ -125,9 +125,6 @@ struct PlanningSettingsView: View {
.onChange(of: tournament.courtCount) { .onChange(of: tournament.courtCount) {
_save() _save()
} }
.onChange(of: tournament.groupStageCourtCount) {
_save()
}
.onChange(of: tournament.dayDuration) { .onChange(of: tournament.dayDuration) {
_save() _save()
} }

@ -42,7 +42,7 @@ struct RoundSettingsView: View {
let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex) let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex) let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
let matches = (0..<matchCount).map { //0 is final match let matches = (0..<matchCount).map { //0 is final match
return Match(round: round.id, index: $0 + matchStartIndex, matchFormat: round.matchFormat) return Match(round: round.id, index: $0 + matchStartIndex, matchFormat: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: $0))
} }
try? dataStore.rounds.addOrUpdate(instance: round) try? dataStore.rounds.addOrUpdate(instance: round)
try? dataStore.matches.addOrUpdate(contentOfs: matches) try? dataStore.matches.addOrUpdate(contentOfs: matches)

@ -52,9 +52,10 @@ struct RoundView: View {
print(spaceLeft[drawResult.drawIndex].matchTitle()) print(spaceLeft[drawResult.drawIndex].matchTitle())
availableQualifiedTeams[drawResult.drawee].setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true) availableQualifiedTeams[drawResult.drawee].setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
} }
try? dataStore.matches.addOrUpdate(contentOfs: spaceLeft) _save()
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: availableQualifiedTeams) if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
isEditingTournamentSeed.wrappedValue.toggle() self.isEditingTournamentSeed.wrappedValue = false
}
} }
} }
} else if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) { } else if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) {
@ -62,8 +63,8 @@ struct RoundView: View {
Section { Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup)
if tournament.availableSeeds().isEmpty {
_save() _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false self.isEditingTournamentSeed.wrappedValue = false
} }
} }
@ -109,8 +110,10 @@ struct RoundView: View {
print(availableSeedSpot[drawResult.drawIndex].matchTitle()) print(availableSeedSpot[drawResult.drawIndex].matchTitle())
seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false)
} }
try? dataStore.matches.addOrUpdate(contentOfs: availableSeedSpot) _save()
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: seeds) if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
} }
} }
} }
@ -129,7 +132,16 @@ struct RoundView: View {
} }
private func _save() { private func _save() {
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.seeds()) try? dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
//todo should be done server side
let rounds = tournament.rounds()
rounds.forEach { round in
let matches = round.playedMatches()
matches.forEach { match in
match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound())
}
}
let allRoundMatches = tournament.allRoundMatches() let allRoundMatches = tournament.allRoundMatches()
try? DataStore.shared.matches.addOrUpdate(contentOfs: allRoundMatches) try? DataStore.shared.matches.addOrUpdate(contentOfs: allRoundMatches)
} }

@ -30,7 +30,7 @@ struct BroadcastView: View {
List { List {
Section { Section {
TipView(tournamentPublishingTip) { action in TipView(tournamentPublishingTip) { action in
UIApplication.shared.open(URLs.padelClub.url) UIApplication.shared.open(URLs.main.url)
} }
.tipStyle(tint: nil) .tipStyle(tint: nil)
} }
@ -39,67 +39,144 @@ struct BroadcastView: View {
.tipStyle(tint: nil) .tipStyle(tint: nil)
} }
if tournament.publishManually == false {
Section { Section {
Text("En mode automatique, Padel Club détermine le meilleur moment pour publier les informations du tournoi.\n\n• La liste des équipes sera visible au maximum 2 jours après la clôture des inscriptions. À partir des p500, la liste d'attente à la clôture sera également publiée.\n\n• Les convocations ne seront visibles qu'à partir du premier jour du tournoi.\n\n• Les poules ne seront visibles qu'au premier jour des poules.\n\n• Le tableau ne sera visible qu'au premier jour du tableau.") 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")
}
} header: { } header: {
Text("Mode Automatique") Text("Liste des équipes")
} footer: { } footer: {
FooterButtonView("désactiver le mode automatique") { if Date() < tournament.publishedTeamsDate() {
tournament.publishManually.toggle() HStack {
Spacer()
FooterButtonView(tournament.publishTeams ? "masquer sur le site" : "publier maintenant") {
tournament.publishTeams.toggle()
}
} }
} }
} }
if tournament.publishManually == true {
Section { Section {
Text("En mode manuel, vous devez indiquer à Padel Club le moment où vous souhaitez publier les informations du tournoi") 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()
}
}
}
}
Toggle(isOn: $tournament.publishTeams) { if let publishedGroupStagesDate = tournament.publishedGroupStagesDate() {
Text("Publier la liste des équipes") Section {
Text("Les horaires de convocations ne seront pas publiés") 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()
}
} }
Toggle(isOn: $tournament.publishWaitingList) {
Text("Inclure les équipes en liste d'attente")
} }
Toggle(isOn: $tournament.publishSummons) {
Text("Publier les convocations")
} }
Toggle(isOn: $tournament.publishGroupStages) {
Text("Publier les poules")
} }
Toggle(isOn: $tournament.publishBrackets) {
Text("Publier le tableau") 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: { } header: {
Text("Mode Manuel") Text("Tableau")
} footer: { } footer: {
FooterButtonView("activer le mode automatique") { if Date() < publishedBracketsDate {
tournament.publishManually.toggle() HStack {
Spacer()
FooterButtonView(tournament.publishBrackets ? "masquer sur le site" : "publier maintenant") {
tournament.publishBrackets.toggle()
}
} }
} }
} }
}
//todo waitinglist & info
Section { Section {
Toggle(isOn: $tournament.isPrivate) { Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privé") Text("Tournoi privé")
} }
} footer: { } footer: {
Text("Le tournoi sera masqué sur le site \(URLs.main.rawValue)") let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))"
Text(.init(footerString))
} }
Section { Section {
LabeledContent { LabeledContent {
actionForURL(URLs.main.url) actionForURL(URLs.main.url)
} label: { } label: {
Text("Lien Padel Club") Text("Padel Club")
} }
if let club = tournament.club(), let clubURL = club.shareURL() { if let club = tournament.club(), let clubURL = club.shareURL() {
LabeledContent { LabeledContent {
actionForURL(clubURL) actionForURL(clubURL)
} label: { } label: {
Text("Lien du club") Text("Club")
} }
} }
@ -107,7 +184,7 @@ struct BroadcastView: View {
LabeledContent { LabeledContent {
actionForURL(url) actionForURL(url)
} label: { } label: {
Text("Lien du tournoi") Text("Tournoi")
} }
} }
@ -115,7 +192,7 @@ struct BroadcastView: View {
LabeledContent { LabeledContent {
actionForURL(url) actionForURL(url)
} label: { } label: {
Text("Lien TV") Text("TV")
} }
} }
@ -139,7 +216,7 @@ struct BroadcastView: View {
UIPasteboard.general.string = urlToShow UIPasteboard.general.string = urlToShow
} }
} }
.onChange(of: tournament.isPrivate) { .onChange(of: [tournament.isPrivate, tournament.publishTeams, tournament.publishSummons, tournament.publishBrackets, tournament.publishGroupStages]) {
_save() _save()
} }
} }
@ -185,8 +262,7 @@ struct BroadcastView: View {
} label: { } label: {
HStack { HStack {
Spacer() Spacer()
Text("lien") Image(systemName: "square.and.arrow.up")
.underline()
} }
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

@ -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, groupStageCourtCount: 6, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: 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)
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)
@ -117,7 +117,6 @@ final class ServerDataTests: XCTestCase {
assert(t.federalCategory == tournament.federalCategory) assert(t.federalCategory == tournament.federalCategory)
assert(t.federalLevelCategory == tournament.federalLevelCategory) assert(t.federalLevelCategory == tournament.federalLevelCategory)
assert(t.federalAgeCategory == tournament.federalAgeCategory) assert(t.federalAgeCategory == tournament.federalAgeCategory)
assert(t.groupStageCourtCount == tournament.groupStageCourtCount)
assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted()) assert(t.closedRegistrationDate?.formatted() == tournament.closedRegistrationDate?.formatted())
assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified) assert(t.groupStageAdditionalQualified == tournament.groupStageAdditionalQualified)
assert(t.courtCount == tournament.courtCount) assert(t.courtCount == tournament.courtCount)
@ -127,6 +126,10 @@ final class ServerDataTests: XCTestCase {
assert(t.entryFee == tournament.entryFee) assert(t.entryFee == tournament.entryFee)
assert(t.additionalEstimationDuration == tournament.additionalEstimationDuration) assert(t.additionalEstimationDuration == tournament.additionalEstimationDuration)
assert(t.isDeleted == tournament.isDeleted) assert(t.isDeleted == tournament.isDeleted)
assert(t.publishTeams == tournament.publishTeams)
assert(t.publishSummons == tournament.publishSummons)
assert(t.publishGroupStages == tournament.publishGroupStages)
assert(t.publishBrackets == tournament.publishBrackets)
} }

Loading…
Cancel
Save