Laurent 1 year ago
commit fd4adf470f
  1. 8
      PadelClub.xcodeproj/project.pbxproj
  2. 22
      PadelClub/Data/GroupStage.swift
  3. 20
      PadelClub/Data/Match.swift
  4. 4
      PadelClub/Data/PlayerRegistration.swift
  5. 18
      PadelClub/Data/TeamRegistration.swift
  6. 28
      PadelClub/Data/Tournament.swift
  7. 4
      PadelClub/HTML Templates/tournament-template.html
  8. 1
      PadelClub/Utils/HtmlGenerator.swift
  9. 16
      PadelClub/Utils/HtmlService.swift
  10. 2
      PadelClub/Utils/Tips.swift
  11. 33
      PadelClub/Utils/URLs.swift
  12. 50
      PadelClub/Views/Calling/SendToAllView.swift
  13. 60
      PadelClub/Views/Cashier/Event/EventLinksView.swift
  14. 9
      PadelClub/Views/Cashier/Event/EventView.swift
  15. 97
      PadelClub/Views/Components/FortuneWheelView.swift
  16. 32
      PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift
  17. 49
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  18. 76
      PadelClub/Views/GroupStage/GroupStageView.swift
  19. 15
      PadelClub/Views/GroupStage/GroupStagesView.swift
  20. 104
      PadelClub/Views/Round/RoundView.swift
  21. 5
      PadelClub/Views/Round/RoundsView.swift
  22. 18
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  23. 4
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  24. 13
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  25. 2
      PadelClub/Views/Tournament/TournamentBuildView.swift
  26. 4
      PadelClub/Views/Tournament/TournamentRunningView.swift

@ -142,6 +142,7 @@
FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-template.html */; };
FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; };
FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; };
FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; };
FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; };
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; };
FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; };
@ -462,6 +463,7 @@
FF1F4B7E2BFA0105000B4573 /* player-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "player-template.html"; sourceTree = "<group>"; };
FF1F4B7F2BFA0105000B4573 /* tournament-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "tournament-template.html"; sourceTree = "<group>"; };
FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = "<group>"; };
FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = "<group>"; };
FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = "<group>"; };
FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
@ -1212,6 +1214,7 @@
isa = PBXGroup;
children = (
FFBF41812BF73EB3001B24CB /* EventView.swift */,
FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */,
FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */,
FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */,
FF8F263A2BAD528600650388 /* EventCreationView.swift */,
@ -1553,6 +1556,7 @@
FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */,
FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */,
FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */,
FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */,
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */,
FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */,
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */,
@ -1927,7 +1931,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 25;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1965,7 +1969,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 25;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -50,7 +50,7 @@ class GroupStage: ModelObject, Storable {
}
func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if let name { return "Poule " + name }
if let name { return name }
switch displayStyle {
case .wide:
return "Poule \(index + 1)"
@ -170,21 +170,21 @@ class GroupStage: ModelObject, Storable {
return _matches().first(where: { matchIndexes.contains($0.index) })
}
func availableToStart() -> [Match] {
let runningMatches = runningMatches()
return playedMatches().filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false })
func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] {
return []
return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false })
}
func runningMatches() -> [Match] {
playedMatches().filter({ $0.isRunning() })
func runningMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
}
func readyMatches() -> [Match] {
playedMatches().filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
func readyMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
}
func finishedMatches() -> [Match] {
playedMatches().filter({ $0.hasEnded() })
func finishedMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()
}
private func _matchOrder() -> [Int] {
@ -366,7 +366,7 @@ extension GroupStage: Selectable {
}
func badgeValue() -> Int? {
runningMatches().count
runningMatches(playedMatches: _matches()).count
}
func badgeValueColor() -> Color? {

@ -486,11 +486,11 @@ class Match: ModelObject, Storable {
} catch {
Logger.error(error)
}
followingMatch()?.resetTeamScores()
_loserMatch()?.resetTeamScores()
}
followingMatch()?.resetTeamScores()
_loserMatch()?.resetTeamScores()
}
func createTeamScores() -> [TeamScore] {
let teamOne = team(.one)
let teamTwo = team(.two)
@ -568,15 +568,15 @@ class Match: ModelObject, Storable {
}
func canBeStarted(inMatches matches: [Match]) -> Bool {
let teams = teams()
let teams = teamScores
guard teams.count == 2 else { return false }
guard hasEnded() == false else { return false }
guard hasStarted() == false else { return false }
return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false })
return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false })
}
func isTeamPlaying(_ team: TeamRegistration, inMatches matches: [Match]) -> Bool {
matches.filter({ $0.teams().contains(team) }).isEmpty == false
matches.filter({ $0.teamScores.compactMap { $0.teamRegistration }.contains(team.id) }).isEmpty == false
}
var computedStartDateForSorting: Date {
@ -588,15 +588,17 @@ class Match: ModelObject, Storable {
}
func hasSpaceLeft() -> Bool {
teams().count == 1
teamScores.count < 2
}
func isReady() -> Bool {
teams().count == 2
teamScores.count == 2
// teams().count == 2
}
func isEmpty() -> Bool {
teams().isEmpty
teamScores.isEmpty
// teams().isEmpty
}
func hasEnded() -> Bool {

@ -74,8 +74,8 @@ class PlayerRegistration: ModelObject, Storable {
}
internal init(federalData: [String], sex: Int, sexUnknown: Bool) {
lastName = federalData[0].trimmed.capitalized
firstName = federalData[1].trimmed.uppercased()
lastName = federalData[0].trimmed.uppercased()
firstName = federalData[1].trimmed.capitalized
birthdate = federalData[2]
licenceId = federalData[3]
clubName = federalData[4]

@ -117,7 +117,19 @@ class TeamRegistration: ModelObject, Storable {
func teamScores() -> [TeamScore] {
Store.main.filter(isIncluded: { $0.teamRegistration == id })
}
func wins() -> [Match] {
Store.main.filter(isIncluded: { $0.winningTeamId == id })
}
func loses() -> [Match] {
Store.main.filter(isIncluded: { $0.losingTeamId == id })
}
func matches() -> [Match] {
Store.main.filter(isIncluded: { $0.losingTeamId == id || $0.winningTeamId == id })
}
var tournamentCategory: TournamentCategory {
tournamentObject()?.tournamentCategory ?? .men
}
@ -171,7 +183,7 @@ class TeamRegistration: ModelObject, Storable {
}
func canPlay() -> Bool {
teamScores().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived })
matches().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived })
}
func availableForSeedPick() -> Bool {
@ -216,8 +228,10 @@ class TeamRegistration: ModelObject, Storable {
func updatePlayers(_ players: Set<PlayerRegistration>, inTournamentCategory tournamentCategory: TournamentCategory) {
let previousPlayers = Set(unsortedPlayers())
let playersToRemove = previousPlayers.subtracting(players)
do {
try DataStore.shared.playerRegistrations.delete(contentOfs: unsortedPlayers())
try DataStore.shared.playerRegistrations.delete(contentOfs: playersToRemove)
} catch {
Logger.error(error)
}

@ -404,14 +404,9 @@ class Tournament : ModelObject, Storable {
return false
}
}
func shareURL() -> URL? {
return URLs.main.url.appending(path: "tournament/\(id)")
}
func broadcastURL() -> URL? {
return URLs.main.url.appending(path: "tournament/\(id)/broadcast")
func shareURL(_ pageLink: PageLink = .matches) -> URL? {
return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path)
}
func courtUsed() -> [Int] {
@ -929,8 +924,12 @@ class Tournament : ModelObject, Storable {
return Calendar.current.compare(summonDate, to: expectedSummonDate, toGranularity: .minute) != ComparisonResult.orderedSame
}
func availableToStart(_ allMatches: [Match]) -> [Match] {
let runningMatches = allMatches.filter({ $0.isRunning() && $0.isReady() })
func groupStagesMatches() -> [Match] {
let groupStageIds = groupStages().map { $0.id }
return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
}
func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) -> [Match] {
return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting)
}
@ -1399,15 +1398,14 @@ class Tournament : ModelObject, Storable {
print("Position \(index+1) Poule \(groupStages[jIndex].index)")
chunks[index][jIndex].groupStage = groupStages[jIndex].id
chunks[index][jIndex].groupStagePosition = index
do {
try DataStore.shared.teamRegistrations.addOrUpdate(instance: chunks[index][jIndex])
} catch {
Logger.error(error)
}
}
}
do {
try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
} catch {
Logger.error(error)
}
groupStages.forEach { $0.buildMatches() }
}

@ -80,13 +80,13 @@
}
.player {
font-size:28px;
font-size:26px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hiddenPlayer {
font-size:28px;
font-size:26px;
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;

@ -103,6 +103,7 @@ class HtmlGenerator: ObservableObject {
groupStageDone = 0
groupStageIsReady = false
pdfDocument = PDFDocument()
rects.removeAll()
try? FileManager.default.removeItem(at: pdfURL!)
print("buildPDF", width, height, zoomLevel ?? 0)
if let zoomLevel {

@ -66,7 +66,7 @@ enum HtmlService {
} else {
template = template.replacingOccurrences(of: "{{bracketStartDate}}", with: "")
}
template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle())
template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle(.short))
template = template.replacingOccurrences(of: "{{bracketTitle}}", with: bracket.groupStageTitle())
var col = ""
@ -84,7 +84,7 @@ enum HtmlService {
if let playerOne = entrant.players()[safe: 0] {
template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel())
if withRank {
template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank())")
template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))")
} else {
template = template.replacingOccurrences(of: "{{weightOne}}", with: "")
}
@ -93,7 +93,7 @@ enum HtmlService {
if let playerTwo = entrant.players()[safe: 1] {
template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel())
if withRank {
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank())")
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))")
} else {
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "")
}
@ -133,18 +133,18 @@ enum HtmlService {
case .player(let entrant):
var template = html
if let playerOne = entrant.players()[safe: 0] {
template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel(.short))
template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel())
if withRank {
template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank())")
template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))")
} else {
template = template.replacingOccurrences(of: "{{weightOne}}", with: "")
}
}
if let playerTwo = entrant.players()[safe: 1] {
template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel(.short))
template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel())
if withRank {
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank())")
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))")
} else {
template = template.replacingOccurrences(of: "{{weightTwo}}", with: "")
}
@ -192,7 +192,7 @@ enum HtmlService {
return bracket
case .template(let tournament):
var template = html
template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle())
template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle(.short))
var brackets = ""
for round in tournament.rounds() {
brackets = brackets.appending(HtmlService.bracket(tournament: tournament, roundIndex: round.index).html(headName: headName, withRank: withRank, withScore: withScore))

@ -336,7 +336,7 @@ struct TournamentPublishingTip: Tip {
}
var message: Text? {
Text("Padel Club vous permet de publier votre tournoi et rendre accessible à tous les résultats des matchs et l'évolution de l'événement. Les informations seront accessible sur le site Padel Club.")
Text("Padel Club vous permet de publier votre tournoi et rendre accessible à tous les résultats des matchs et l'évolution de l'événement. Les informations seront accessibles sur le site Padel Club.")
}
var image: Image? {

@ -21,3 +21,36 @@ enum URLs: String, Identifiable {
}
}
enum PageLink: String, Identifiable, CaseIterable {
case teams = "Équipes"
case summons = "Convocations"
case groupStages = "Poules"
case matches = "Matchs"
case rankings = "Classement"
case broadcast = "Broadcast"
var id: String { self.rawValue }
func localizedLabel() -> String {
rawValue
}
var path: String {
switch self {
case .matches:
return ""
case .teams:
return "teams"
case .summons:
return "summons"
case .rankings:
return "rankings"
case .groupStages:
return "group-stages"
case .broadcast:
return "broadcast"
}
}
}

@ -9,6 +9,8 @@ import SwiftUI
import LeStorage
struct SendToAllView: View {
@Environment(\.dismiss) var dismiss
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
@EnvironmentObject var networkMonitor: NetworkMonitor
@ -18,7 +20,8 @@ struct SendToAllView: View {
@State private var sentError: ContactManagerError? = nil
let addLink: Bool
@State var cannotPayForTournament: Bool = false
@State private var pageLink: PageLink = .teams
var messageSentFailed: Binding<Bool> {
Binding {
sentError != nil
@ -69,12 +72,34 @@ struct SendToAllView: View {
}
}
if addLink {
Section {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel())
}
} label: {
Text("Choisir une page du tournoi en particulier")
}
.pickerStyle(.menu)
}
}
Section {
RowButtonView("Contacter \(_totalString())") {
self._contactAndPay()
}
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
dismiss()
}
}
}
.environment(\.editMode, Binding.constant(EditMode.active))
.headerProminence(.increased)
.navigationTitle("Préparation")
@ -141,7 +166,10 @@ struct SendToAllView: View {
}
func _teams() -> [TeamRegistration] {
_roundTeams() + _groupStagesTeams()
if _roundTeams().isEmpty && _groupStagesTeams().isEmpty {
return tournament.selectedSortedTeams()
}
return _roundTeams() + _groupStagesTeams()
}
func _roundTeams() -> [TeamRegistration] {
@ -172,11 +200,25 @@ struct SendToAllView: View {
}
}
func finalMessage() -> String {
var message = [String?]()
message.append("\n\n")
if addLink {
message.append(tournament.shareURL(pageLink)?.absoluteString)
}
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature()
message.append(signature)
return message.compactMap { $0 }.joined(separator: "\n\n")
}
fileprivate func _contact() {
if contactMethod == 0 {
contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: addLink ? tournament.shareURL()?.absoluteString : nil, tournamentBuild: nil)
contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: finalMessage(), tournamentBuild: nil)
} else {
contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: addLink ? tournament.shareURL()?.absoluteString : nil, subject: tournament.tournamentTitle(), tournamentBuild: nil)
contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: tournament.tournamentTitle(), tournamentBuild: nil)
}
}

@ -0,0 +1,60 @@
//
// EventLinksView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 26/05/2024.
//
import SwiftUI
struct EventLinksView: View {
let event: Event
@State private var pageLink: PageLink = .teams
func eventLinksPasteData() -> String {
var link = [String]()
link.append(event.eventTitle())
event.tournaments.forEach({ tournament in
if let url = tournament.shareURL(pageLink) {
var tournamentLink = [String]()
tournamentLink.append(tournament.tournamentTitle())
tournamentLink.append(url.absoluteString)
link.append(tournamentLink.joined(separator: "\n"))
}
})
return link.joined(separator: "\n\n")
}
var body: some View {
List {
Section {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel())
}
} label: {
Text("Choisir une page du tournoi en particulier")
}
.pickerStyle(.menu)
}
let eventLinksPasteData = eventLinksPasteData()
Section {
Text(eventLinksPasteData)
.italic()
.multilineTextAlignment(.leading)
ShareLink("Partagez ce message", item: eventLinksPasteData)
}
}
}
}
#Preview {
EventLinksView(event: Event.mock())
}

@ -9,6 +9,7 @@ import SwiftUI
import LeStorage
enum EventDestination: Identifiable, Selectable {
case links
case tournaments(Event)
case cashier
@ -18,6 +19,8 @@ enum EventDestination: Identifiable, Selectable {
func selectionLabel() -> String {
switch self {
case .links:
return "Liens"
case .tournaments:
return "Épreuves"
case .cashier:
@ -27,6 +30,8 @@ enum EventDestination: Identifiable, Selectable {
func badgeValue() -> Int? {
switch self {
case .links:
return nil
case .tournaments(let event):
return event.tournaments.count
case .cashier:
@ -49,7 +54,7 @@ struct EventView: View {
@State private var selectedDestination: EventDestination?
func allDestinations() -> [EventDestination] {
[.tournaments(event), .cashier]
[.links, .tournaments(event), .cashier]
}
var body: some View {
@ -60,6 +65,8 @@ struct EventView: View {
EventSettingsView(event: event)
case .some(let selectedEventDestination):
switch selectedEventDestination {
case .links:
EventLinksView(event: event)
case .tournaments(let event):
EventTournamentsView(event: event)
case .cashier:

@ -61,16 +61,14 @@ struct SpinDrawView: View {
let drawees: [any SpinDrawable]
@State var segments: [any SpinDrawable]
let completion: ([DrawResult]) -> Void // Completion closure
var autoMode: Bool = false
let completion: ([DrawResult]) async -> Void // Completion closure
@State private var drawCount: Int = 0
@State private var draws: [DrawResult] = [DrawResult]()
@State private var drawOptions: [DrawOption] = [DrawOption]()
@State private var selectedIndex: Int?
var autoMode: Bool {
drawees.count > 1
}
@State private var disabled: Bool = false
var body: some View {
List {
@ -79,7 +77,7 @@ struct SpinDrawView: View {
_validationLabelView(drawee: drawCount, result: segments[draws.last!.drawIndex])
if autoMode == false || drawCount == drawees.count {
RowButtonView("Valider le tirage") {
completion(draws)
await completion(draws)
dismiss()
}
} else {
@ -92,27 +90,49 @@ struct SpinDrawView: View {
}
Section {
FortuneWheelContainerView(segments: drawOptions, autoMode: autoMode) { index in
self.selectedIndex = index
self.draws.append(DrawResult(drawee: drawCount, drawIndex: drawOptions[index].initialIndex))
self.drawOptions.remove(at: index)
if autoMode && drawCount < drawees.count {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.drawCount += 1
if drawOptions.count == 1 {
self.draws.append(DrawResult(drawee: self.drawCount, drawIndex: self.drawOptions[0].initialIndex))
self.drawOptions.remove(at: 0)
ZStack {
FortuneWheelContainerView(segments: drawOptions, autoMode: autoMode) { index in
self.selectedIndex = index
self.draws.append(DrawResult(drawee: drawCount, drawIndex: drawOptions[index].initialIndex))
self.drawOptions.remove(at: index)
if autoMode && drawCount < drawees.count {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.drawCount += 1
self.selectedIndex = nil
} else {
self.selectedIndex = nil
if drawOptions.count == 1 {
self.draws.append(DrawResult(drawee: self.drawCount, drawIndex: self.drawOptions[0].initialIndex))
self.drawOptions.remove(at: 0)
self.drawCount += 1
self.selectedIndex = nil
} else {
self.selectedIndex = nil
}
}
}
}
.simultaneousGesture(
DragGesture().onChanged({ (value) in
disabled = true
}).onEnded({ (value) in
})
)
Rectangle()
.fill(.white.opacity(0.01))
.clipShape(Circle())
.allowsHitTesting(disabled)
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
} footer: {
HStack {
Spacer()
if autoMode {
Text("Mode automatique")
} else {
Text("Lancer la roue en glissant avec le doigt").multilineTextAlignment(.center)
}
Spacer()
}
}
} else {
Section {
@ -123,7 +143,7 @@ struct SpinDrawView: View {
}
RowButtonView("Valider les tirages") {
completion(draws)
await completion(draws)
dismiss()
}
}
@ -142,6 +162,7 @@ struct SpinDrawView: View {
Button("Annuler", role: .cancel) {
dismiss()
}
.disabled(disabled || autoMode)
}
}
.navigationBarBackButtonHidden()
@ -151,6 +172,7 @@ struct SpinDrawView: View {
.toolbar(.hidden, for: .tabBar)
.listStyle(.insetGrouped)
.scrollDisabled(true)
.interactiveDismissDisabled()
.onAppear {
for (index, segment) in segments.enumerated() {
drawOptions.append(DrawOption(initialIndex: index, option: segment))
@ -162,19 +184,24 @@ struct SpinDrawView: View {
private func _segmentLabelView(segment: [String], horizontalAlignment: HorizontalAlignment = .leading) -> some View {
VStack(alignment: horizontalAlignment, spacing: 0.0) {
ForEach(segment, id: \.self) { string in
Text(string)
Text(string).font(.title3)
.frame(maxWidth: .infinity)
.lineLimit(1)
}
}
}
@ViewBuilder
private func _validationLabelView(drawee: Int, result: SpinDrawable) -> some View {
HStack(spacing: 0.0) {
VStack(spacing: 0.0) {
let draw = drawees[drawee]
_segmentLabelView(segment: draw.segmentLabel(.wide), horizontalAlignment: .leading)
Image(systemName: "arrowshape.forward.fill")
_segmentLabelView(segment: result.segmentLabel(.wide), horizontalAlignment: .trailing)
_segmentLabelView(segment: draw.segmentLabel(.wide), horizontalAlignment: .center)
if result as? TeamRegistration != nil {
Image(systemName: "flag.2.crossed.fill").font(.largeTitle).foregroundColor(.logoRed)
} else {
Image(systemName: "arrowshape.down.fill").font(.largeTitle).foregroundColor(.logoRed)
}
_segmentLabelView(segment: result.segmentLabel(.wide), horizontalAlignment: .center)
}
}
}
@ -200,26 +227,24 @@ struct FortuneWheelContainerView: View {
.onAppear {
if autoMode {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
rotation = 0
rollWheel()
}
}
}
.gesture(
DragGesture()
.onChanged { value in
// Calculate rotation based on the velocity of the drag
let initialVelocity = value.predictedEndTranslation.width / 10 // Adjust sensitivity
rotation += Double(initialVelocity)
}
.onEnded { value in
// Roll the wheel when drag ends
rollWheel()
DragGesture().onChanged({ (value) in
if value.translation.width < 0 {
rotation = Double(-value.translation.width)
}
}).onEnded({ (value) in
rollWheel()
})
)
}
func rollWheel() {
rotation = 0
//rotation = 0
// Generate a random angle for the wheel to rotate
let randomAngle = Double.random(in: 1440...2880) // Adjust range for more or less rotations

@ -23,22 +23,22 @@ struct GroupStageTeamView: View {
}
if groupStage.tournamentObject()?.hasEnded() == false {
if team.qualified && team.bracketPosition == nil, let tournament = team.tournamentObject() {
Section {
NavigationLink {
SpinDrawView(drawees: [team], segments: tournament.matchesWithSpace()) { results in
}
} label: {
Text("Tirage au sort visuel")
}
}
Section {
RowButtonView("Tirage au sort automatique", role: .destructive) {
}
}
}
// if team.qualified && team.bracketPosition == nil, let tournament = team.tournamentObject() {
// Section {
// NavigationLink {
// SpinDrawView(drawees: [team], segments: tournament.matchesWithSpace()) { results in
//
// }
// } label: {
// Text("Tirage au sort visuel")
// }
// }
//
// Section {
// RowButtonView("Tirage au sort automatique", role: .destructive) {
// }
// }
// }
if team.qualified == false {
Section {

@ -11,7 +11,6 @@ import LeStorage
struct GroupStageSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
@State private var nameAlphabetical: Bool = false
@State private var generationDone: Bool = false
var body: some View {
@ -60,9 +59,36 @@ struct GroupStageSettingsView: View {
Text("Redistribue les équipes par la méthode du serpentin")
}
Toggle(isOn: $nameAlphabetical) {
Text("Nommer les poules alphabétiquement")
Section {
RowButtonView("Nommer les poules alphabétiquement", role: .destructive) {
let groupStages = tournament.groupStages()
groupStages.forEach { groupStage in
if let letter = Alphabet.letterForIndex(index: groupStage.index) {
groupStage.name = "Poule " + letter
}
}
do {
try dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
} catch {
Logger.error(error)
}
}
}
Section {
RowButtonView("Supprimer les noms des poules", role: .destructive) {
let groupStages = tournament.groupStages()
groupStages.forEach { groupStage in
groupStage.name = nil
}
do {
try dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
} catch {
Logger.error(error)
}
}
}
}
.overlay(alignment: .bottom) {
if generationDone {
@ -71,23 +97,6 @@ struct GroupStageSettingsView: View {
.deferredRendering(for: .seconds(2))
}
}
.onChange(of: nameAlphabetical) {
let groupStages = tournament.groupStages()
if nameAlphabetical {
groupStages.forEach { groupStage in
groupStage.name = Alphabet.letterForIndex(index: groupStage.index)
}
} else {
groupStages.forEach { groupStage in
groupStage.name = nil
}
}
do {
try dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
} catch {
Logger.error(error)
}
}
}

@ -15,11 +15,11 @@ struct GroupStageView: View {
@State private var sortingMode: GroupStageSortingMode = .auto
@State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false
@State private var groupStageName: String = ""
let playedMatches: [Match]
init(groupStage: GroupStage) {
self.groupStage = groupStage
_groupStageName = State(wrappedValue: groupStage.groupStageTitle())
self.playedMatches = groupStage.playedMatches()
}
var body: some View {
@ -44,21 +44,18 @@ struct GroupStageView: View {
}
.headerProminence(.increased)
MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID())
MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID())
MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID())
MatchListView(section: "terminés", matches: groupStage.finishedMatches(), isExpanded: false).id(UUID())
}
.onChange(of: groupStageName) {
groupStage.name = groupStageName
_save()
let runningMatches = groupStage.runningMatches(playedMatches: playedMatches)
MatchListView(section: "disponible", matches: groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches))
MatchListView(section: "en cours", matches: runningMatches)
MatchListView(section: "à lancer", matches: groupStage.readyMatches(playedMatches: playedMatches))
MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
_groupStageMenuView()
}
}
.navigationTitle($groupStageName)
.navigationTitle(groupStage.groupStageTitle())
}
private enum GroupStageSortingMode {
@ -178,12 +175,10 @@ struct GroupStageView: View {
private func _groupStageMenuView() -> some View {
Menu {
if groupStage.name != nil {
Button("Retirer le nom") {
groupStage.name = nil
groupStageName = groupStage.groupStageTitle()
_save()
}
NavigationLink {
GroupStageNameEditionView(groupStage: groupStage)
} label: {
Label("Renommer", systemImage: "pencil")
}
Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true
@ -225,3 +220,48 @@ struct GroupStageView: View {
}
}
}
struct GroupStageNameEditionView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var dataStore: DataStore
let groupStage: GroupStage
@State private var groupStageName: String = ""
var body: some View {
Form {
Section {
TextField("Nom de la poule", text: $groupStageName)
.keyboardType(.alphabet)
.frame(maxWidth: .infinity)
.onAppear(perform: {
groupStageName = groupStage.name ?? ""
})
.onSubmit {
groupStageName = groupStageName.trimmed
groupStage.name = groupStageName
_save()
dismiss()
}
} footer: {
HStack {
Spacer()
FooterButtonView("retirer le nom") {
groupStage.name = nil
groupStageName = groupStage.groupStageTitle()
_save()
}
}
}
}
.navigationTitle(groupStage.groupStageTitle())
.toolbarBackground(.visible, for: .navigationBar)
}
private func _save() {
do {
try dataStore.groupStages.addOrUpdate(instance: groupStage)
} catch {
Logger.error(error)
}
}
}

@ -51,8 +51,12 @@ struct GroupStagesView: View {
}
}
let allMatches: [Match]
init(tournament: Tournament) {
self.tournament = tournament
self.allMatches = tournament.groupStagesMatches()
if tournament.shouldVerifyGroupStage {
_selectedDestination = State(wrappedValue: nil)
} else if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty {
@ -77,15 +81,14 @@ struct GroupStagesView: View {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination {
case .all:
let allGroupStages = tournament.groupStages()
let availableToStart = allGroupStages.flatMap({ $0.availableToStart() })
let runningMatches = allGroupStages.flatMap({ $0.runningMatches() })
let readyMatches = allGroupStages.flatMap({ $0.readyMatches() })
let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() })
let runningMatches = tournament.runningMatches(allMatches)
let availableToStart = tournament.availableToStart(allMatches, in: runningMatches)
let readyMatches = tournament.readyMatches(allMatches)
let finishedMatches = tournament.finishedMatches(allMatches)
List {
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false)
}

@ -31,6 +31,7 @@ struct RoundView: View {
let availableQualifiedTeams = tournament.availableQualifiedTeams()
let displayableMatches = round.displayableMatches().sorted(by: \.index)
let spaceLeft = displayableMatches.filter({ $0.hasSpaceLeft() })
let seedSpaceLeft = displayableMatches.filter({ $0.isEmpty() })
if isEditingTournamentSeed.wrappedValue == false {
//(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue })
if loserRounds.isEmpty == false {
@ -45,41 +46,82 @@ struct RoundView: View {
}
}
}
} else if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false {
NavigationLink("Tirer au sort la position d'un qualifié") {
SpinDrawView(drawees: availableQualifiedTeams, segments: spaceLeft) { results in
results.forEach { drawResult in
print(availableQualifiedTeams[drawResult.drawee].teamLabel())
print(spaceLeft[drawResult.drawIndex].matchTitle())
availableQualifiedTeams[drawResult.drawee].setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
} else {
if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) {
Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup)
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
}
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
} footer: {
if availableSeedGroup.isFixed() == false {
Text("Le tirage au sort ne sera pas visuel. Toutes les équipes de ce chapeau seront tirées.")
}
}
if (availableSeedGroup.isFixed() == false) {
Section {
RowButtonView("Tirage au sort \(availableSeedGroup.localizedLabel()) visuel") {
self.selectedSeedGroup = availableSeedGroup
}
} footer: {
Text("Le tirage au sort sera visuel et automatique, n'hésitez pas à enregistrer une vidéo de votre écran. Toutes les équipes de ce chapeau seront tirées les unes après les autres.")
}
}
}
} else if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) {
Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup)
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false {
Section {
ForEach(availableQualifiedTeams) { team in
NavigationLink {
SpinDrawView(drawees: [team], segments: spaceLeft) { results in
Task {
results.forEach { drawResult in
team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
}
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
}
}
} label: {
TeamRowView(team: team, displayCallDate: false)
}
}
} header: {
Text("Tirage au sort visuel d'un qualifié").font(.subheadline)
}
}
if (availableSeedGroup.isFixed() == false) {
if tournament.availableSeeds().isEmpty == false && seedSpaceLeft.isEmpty == false {
Section {
RowButtonView("Tirage au sort \(availableSeedGroup.localizedLabel()) visuel") {
self.selectedSeedGroup = availableSeedGroup
ForEach(tournament.availableSeeds()) { team in
NavigationLink {
SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in
Task {
results.forEach { drawResult in
team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false)
}
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
}
}
} label: {
TeamRowView(team: team, displayCallDate: false)
}
}
} header: {
Text("Tirage au sort visuel d'une tête de série").font(.subheadline)
}
}
}
ForEach(displayableMatches) { match in
Section {
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
@ -100,20 +142,20 @@ struct RoundView: View {
}
}
}
.sheet(isPresented: showVisualDrawView) {
.fullScreenCover(isPresented: showVisualDrawView) {
if let availableSeedGroup = selectedSeedGroup {
let seeds = tournament.seeds(inSeedGroup: availableSeedGroup)
let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: round.index)
NavigationStack {
SpinDrawView(drawees: seeds, segments: availableSeedSpot) { draws in
draws.forEach { drawResult in
print(seeds[drawResult.drawee].teamLabel())
print(availableSeedSpot[drawResult.drawIndex].matchTitle())
seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false)
}
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in
Task {
draws.forEach { drawResult in
seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false)
}
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
}
}
}

@ -14,12 +14,13 @@ struct RoundsView: View {
init(tournament: Tournament) {
self.tournament = tournament
if tournament.shouldVerifyBracket {
let availableSeeds = tournament.availableSeeds()
if tournament.shouldVerifyBracket && availableSeeds.isEmpty {
_selectedRound = State(wrappedValue: nil)
} else {
_selectedRound = State(wrappedValue: tournament.getActiveRound())
}
if tournament.availableSeeds().isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false {
if availableSeeds.isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false {
_isEditingTournamentSeed = State(wrappedValue: true)
}
}

@ -21,7 +21,8 @@ struct BroadcastView: View {
let filter = CIFilter.qrCodeGenerator()
@State private var urlToShow: String?
@State private var tvMode: Bool = false
@State private var pageLink: PageLink = .teams
let tournamentPublishingTip = TournamentPublishingTip()
let tournamentTVBroadcastTip = TournamentTVBroadcastTip()
@ -180,15 +181,26 @@ struct BroadcastView: View {
}
}
if let url = tournament.shareURL() {
if let url = tournament.shareURL(pageLink) {
LabeledContent {
actionForURL(url)
} label: {
Text("Tournoi")
Text(pageLink.localizedLabel())
}
}
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings]
Picker(selection: $pageLink) {
ForEach(links) { pageLink in
Text(pageLink.localizedLabel()).tag(pageLink)
}
} label: {
Text("Modifier la page du tournoi à partager")
}
.pickerStyle(.menu)
if let url = tournament.broadcastURL() {
if let url = tournament.shareURL(.broadcast) {
LabeledContent {
actionForURL(url)
} label: {

@ -81,7 +81,7 @@ struct TournamentStatusView: View {
dismiss()
}
} footer: {
Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main)"))
Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main.rawValue))"))
}
}
@ -90,7 +90,7 @@ struct TournamentStatusView: View {
Text("Tournoi privé")
}
} footer: {
Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main)"))
Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))"))
}
}
.toolbarBackground(.visible, for: .navigationBar)

@ -608,15 +608,20 @@ struct InscriptionManagerView: View {
Section {
let unsortedTeams = tournament.unsortedTeams()
let walkoutTeams = tournament.walkoutTeams()
let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
LabeledContent {
Text(unsortedTeams.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
} label: {
Text("Paire\(unsortedTeams.count.pluralSuffix) inscrite\(unsortedTeams.count.pluralSuffix)")
Text("dont \(walkoutTeams.count) forfait\(walkoutTeams.count.pluralSuffix)")
Text("Paire\(unsortedTeamsWithoutWO.count.pluralSuffix) inscrite\(unsortedTeamsWithoutWO.count.pluralSuffix)")
}
LabeledContent {
Text(walkoutTeams.count.formatted()).font(.largeTitle)
} label: {
Text("Forfait\(walkoutTeams.count.pluralSuffix)")
}
let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
LabeledContent {
Text(max(0, unsortedTeamsWithoutWO.count - tournament.teamCount).formatted()).font(.largeTitle)
} label: {

@ -25,6 +25,7 @@ struct TournamentBuildView: View {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
Text(tournament.groupStageStatus())
.multilineTextAlignment(.trailing)
} label: {
Text("Poules")
if tournament.shouldVerifyGroupStage {
@ -38,6 +39,7 @@ struct TournamentBuildView: View {
NavigationLink(value: Screen.round) {
LabeledContent {
Text(tournament.bracketStatus())
.multilineTextAlignment(.trailing)
} label: {
Text("Tableau")
if tournament.shouldVerifyBracket {

@ -19,8 +19,8 @@ struct TournamentRunningView: View {
@ViewBuilder
var body: some View {
MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches))
MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), isExpanded: false)
MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false)
// MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), isExpanded: false)
// MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false)
MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches), isExpanded: false)
}
}

Loading…
Cancel
Save