parent
38d2f7d005
commit
230181fe31
@ -0,0 +1,108 @@ |
||||
// |
||||
// GroupStage_v2.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class GroupStage: ModelObject, Storable { |
||||
static func resourceName() -> String { "group-stages" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var tournament: String |
||||
var index: Int |
||||
var size: Int |
||||
var format: Int? |
||||
var startDate: Date? |
||||
|
||||
var matchFormat: MatchFormat { |
||||
get { |
||||
MatchFormat(rawValue: format ?? 0) ?? .defaultFormatForMatchType(.groupStage) |
||||
} |
||||
set { |
||||
format = newValue.rawValue |
||||
} |
||||
} |
||||
|
||||
internal init(tournament: String, index: Int, size: Int, matchFormat: MatchFormat? = nil, startDate: Date? = nil) { |
||||
self.tournament = tournament |
||||
self.index = index |
||||
self.size = size |
||||
self.format = matchFormat?.rawValue |
||||
self.startDate = startDate |
||||
} |
||||
|
||||
func title(_ displayStyle: DisplayStyle = .wide) -> String { |
||||
switch displayStyle { |
||||
case .wide: |
||||
return "Poule \(index + 1)" |
||||
case .short: |
||||
return "#\(index + 1)" |
||||
} |
||||
} |
||||
|
||||
func isBroadcasted() -> Bool { |
||||
false |
||||
} |
||||
|
||||
func isRunning() -> Bool { // at least a match has started |
||||
matches.anySatisfy({ $0.isRunning() }) |
||||
} |
||||
|
||||
func hasStarted() -> Bool { // meaning at least one match is over |
||||
matches.filter { $0.hasEnded() }.isEmpty == false |
||||
} |
||||
|
||||
func hasEnded() -> Bool { |
||||
if matches.isEmpty { return false } |
||||
return matches.allSatisfy { $0.hasEnded() } |
||||
} |
||||
|
||||
func buildMatches() { |
||||
removeMatches() |
||||
|
||||
var _matches = [Match]() |
||||
for i in 0..<numberOfMatchesToBuild { |
||||
let newMatch = Match(groupStage: id, index: i, matchFormat: matchFormat) |
||||
_matches.append(newMatch) |
||||
} |
||||
|
||||
try? DataStore.shared.matches.append(contentOfs: _matches) |
||||
} |
||||
|
||||
func removeMatches() { |
||||
try? deleteDependencies() |
||||
} |
||||
|
||||
var numberOfMatchesToBuild: Int { |
||||
(size * (size - 1)) / 2 |
||||
} |
||||
|
||||
var matches: [Match] { |
||||
Store.main.filter { $0.groupStage == self.id } |
||||
} |
||||
|
||||
var teams: [TeamRegistration] { |
||||
Store.main.filter { $0.groupStage == self.id } |
||||
} |
||||
|
||||
override func deleteDependencies() throws { |
||||
try Store.main.deleteDependencies(items: self.matches) |
||||
} |
||||
} |
||||
|
||||
extension GroupStage { |
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _tournament = "tournament" |
||||
case _index = "index" |
||||
case _size = "size" |
||||
case _format = "format" |
||||
case _startDate = "startDate" |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,107 @@ |
||||
// |
||||
// Match_v2.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class Match: ModelObject, Storable { |
||||
static func resourceName() -> String { "matches" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var round: String? |
||||
var groupStage: String? |
||||
var startDate: Date? |
||||
var endDate: Date? |
||||
var index: Int |
||||
var format: Int? |
||||
var court: String? |
||||
var servingTeamId: String? |
||||
var winningTeamId: String? |
||||
var losingTeamId: String? |
||||
var broadcasted: Bool |
||||
var name: String? |
||||
var order: Int |
||||
|
||||
internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, court: String? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, broadcasted: Bool = false, name: String? = nil, order: Int = 0) { |
||||
self.round = round |
||||
self.groupStage = groupStage |
||||
self.startDate = startDate |
||||
self.endDate = endDate |
||||
self.index = index |
||||
self.format = matchFormat?.rawValue |
||||
self.court = court |
||||
self.servingTeamId = servingTeamId |
||||
self.winningTeamId = winningTeamId |
||||
self.losingTeamId = losingTeamId |
||||
self.broadcasted = broadcasted |
||||
self.name = name |
||||
self.order = order |
||||
} |
||||
|
||||
func isRunning() -> Bool { // at least a match has started |
||||
hasStarted() && hasEnded() == false |
||||
} |
||||
|
||||
func hasStarted() -> Bool { // meaning at least one match is over |
||||
if let startDate { |
||||
return startDate.timeIntervalSinceNow < 0 |
||||
} |
||||
if hasEnded() { |
||||
return true |
||||
} |
||||
return false |
||||
|
||||
//todo scores |
||||
// if let score { |
||||
// return score.hasEnded == false && score.sets.isEmpty == false |
||||
// } else { |
||||
// return false |
||||
// } |
||||
} |
||||
|
||||
func hasEnded() -> Bool { |
||||
endDate != nil |
||||
} |
||||
|
||||
var roundObject: Round? { |
||||
Store.main.filter { $0.id == self.round }.first |
||||
} |
||||
|
||||
var groupStageObject: GroupStage? { |
||||
Store.main.filter { $0.id == self.groupStage }.first |
||||
} |
||||
|
||||
var isLoserBracket: Bool { |
||||
roundObject?.loser != nil |
||||
} |
||||
|
||||
var teamScores: [TeamScore] { |
||||
Store.main.filter { $0.match == self.id } |
||||
} |
||||
|
||||
override func deleteDependencies() throws { |
||||
try Store.main.deleteDependencies(items: self.teamScores) |
||||
} |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _round = "round" |
||||
case _groupStage = "groupStage" |
||||
case _startDate = "startDate" |
||||
case _endDate = "endDate" |
||||
case _index = "index" |
||||
case _format = "format" |
||||
case _court = "court" |
||||
case _servingTeamId = "servingTeamId" |
||||
case _winningTeamId = "winningTeamId" |
||||
case _losingTeamId = "losingTeamId" |
||||
case _broadcasted = "broadcasted" |
||||
case _name = "name" |
||||
case _order = "order" |
||||
} |
||||
} |
||||
@ -0,0 +1,46 @@ |
||||
// |
||||
// PlayerRegistration.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class PlayerRegistration: ModelObject, Storable { |
||||
static func resourceName() -> String { "player-registrations" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var teamRegistration: String |
||||
var firstName: String |
||||
var lastName: String |
||||
var licenceId: String? |
||||
var rank: Int? |
||||
var hasPaid: Bool |
||||
var unranked: Bool |
||||
|
||||
internal init(teamRegistration: String, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, hasPaid: Bool, unranked: Bool) { |
||||
self.teamRegistration = teamRegistration |
||||
self.firstName = firstName |
||||
self.lastName = lastName |
||||
self.licenceId = licenceId |
||||
self.rank = rank |
||||
self.hasPaid = hasPaid |
||||
self.unranked = unranked |
||||
} |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _teamRegistration = "teamRegistration" |
||||
case _firstName = "firstName" |
||||
case _lastName = "lastName" |
||||
case _licenceId = "licenceId" |
||||
case _rank = "rank" |
||||
case _hasPaid = "hasPaid" |
||||
case _unranked = "unranked" |
||||
|
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
// |
||||
// Round_v2.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class Round: ModelObject, Storable { |
||||
static func resourceName() -> String { "rounds" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var tournament: String |
||||
var index: Int |
||||
var loser: String? |
||||
var format: Int? |
||||
|
||||
internal init(id: String = Store.randomId(), tournament: String, index: Int, loser: String? = nil, format: Int? = nil) { |
||||
self.id = id |
||||
self.tournament = tournament |
||||
self.index = index |
||||
self.loser = loser |
||||
self.format = format |
||||
} |
||||
|
||||
var matches: [Match] { |
||||
Store.main.filter { $0.round == self.id } |
||||
} |
||||
|
||||
|
||||
var loserRound: Round? { |
||||
guard let loser else { return nil } |
||||
return Store.main.findById(loser) |
||||
} |
||||
|
||||
override func deleteDependencies() throws { |
||||
try Store.main.deleteDependencies(items: self.matches) |
||||
if let loserRound { |
||||
try Store.main.deleteDependencies(items: [loserRound]) |
||||
} |
||||
} |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _tournament = "tournament" |
||||
case _index = "index" |
||||
case _loser = "loser" |
||||
case _format = "format" |
||||
} |
||||
} |
||||
@ -0,0 +1,68 @@ |
||||
// |
||||
// TeamRegistration.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class TeamRegistration: ModelObject, Storable { |
||||
static func resourceName() -> String { "team-registrations" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var tournament: String |
||||
var groupStage: String? |
||||
var registrationDate: Date? |
||||
var callDate: Date? |
||||
var bracketPosition: Int? |
||||
var groupStagePosition: Int? |
||||
var comment: String? |
||||
var source: String? |
||||
var sourceValue: String? |
||||
var logo: String? |
||||
var name: String? |
||||
|
||||
internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil) { |
||||
self.tournament = tournament |
||||
self.groupStage = groupStage |
||||
self.registrationDate = registrationDate |
||||
self.callDate = callDate |
||||
self.bracketPosition = bracketPosition |
||||
self.groupStagePosition = groupStagePosition |
||||
self.comment = comment |
||||
self.source = source |
||||
self.sourceValue = sourceValue |
||||
self.logo = logo |
||||
self.name = name |
||||
} |
||||
|
||||
func qualified() -> Bool { |
||||
groupStagePosition != nil && bracketPosition != nil |
||||
} |
||||
|
||||
var playerRegistrations: [PlayerRegistration] { |
||||
Store.main.filter { $0.teamRegistration == self.id } |
||||
} |
||||
|
||||
override func deleteDependencies() throws { |
||||
try Store.main.deleteDependencies(items: self.playerRegistrations) |
||||
} |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _tournament = "tournament" |
||||
case _groupStage = "groupStage" |
||||
case _registrationDate = "registrationDate" |
||||
case _callDate = "callDate" |
||||
case _bracketPosition = "bracketPosition" |
||||
case _groupStagePosition = "groupStagePosition" |
||||
case _comment = "comment" |
||||
case _source = "source" |
||||
case _sourceValue = "sourceValue" |
||||
case _logo = "logo" |
||||
case _name = "name" |
||||
} |
||||
} |
||||
@ -0,0 +1,52 @@ |
||||
// |
||||
// TeamScore.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by razmig on 10/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class TeamScore: ModelObject, Storable { |
||||
|
||||
static func resourceName() -> String { "team-scores" } |
||||
|
||||
var id: String = Store.randomId() |
||||
var match: String |
||||
var teamRegistration: String? |
||||
var playerRegistrations: [String]? |
||||
var score: String? |
||||
var walkOut: Int? |
||||
var luckyLoser: Bool |
||||
|
||||
internal init(match: String, teamRegistration: String? = nil, playerRegistrations: [String]? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Bool) { |
||||
self.match = match |
||||
self.teamRegistration = teamRegistration |
||||
self.playerRegistrations = playerRegistrations |
||||
self.score = score |
||||
self.walkOut = walkOut |
||||
self.luckyLoser = luckyLoser |
||||
} |
||||
|
||||
|
||||
var team: TeamRegistration? { |
||||
guard let teamRegistration else { |
||||
return nil |
||||
} |
||||
return DataStore.shared.teamRegistrations.findById(teamRegistration) |
||||
|
||||
} |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case _id = "id" |
||||
case _match = "match" |
||||
case _teamRegistration = "teamRegistration" |
||||
case _playerRegistrations = "playerRegistrations" |
||||
case _score = "score" |
||||
case _walkOut = "walkOut" |
||||
case _luckyLoser = "luckyLoser" |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,26 @@ |
||||
// |
||||
// Labels.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct LabelOptions: View { |
||||
var body: some View { |
||||
Label("Options", systemImage: "ellipsis.circle") |
||||
} |
||||
} |
||||
|
||||
struct LabelStructure: View { |
||||
var body: some View { |
||||
Label("Structure", systemImage: "hammer") |
||||
} |
||||
} |
||||
|
||||
struct LabelSettings: View { |
||||
var body: some View { |
||||
Label("Réglages", systemImage: "slider.horizontal.3") |
||||
} |
||||
} |
||||
@ -0,0 +1,376 @@ |
||||
// |
||||
// GroupStageView.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 02/03/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct GroupStageView: View { |
||||
@Bindable var groupStage: GroupStage |
||||
// @State private var selectedMenuLink: MenuLink? |
||||
// @State private var canUpdateTournament: Bool = false |
||||
// @AppStorage("showLongLabel") private var showLongLabel: Bool = false |
||||
// @AppStorage("hideRank") private var hideRank: Bool = false |
||||
@State private var confirmGroupStageStart: Bool = false |
||||
|
||||
enum MenuLink: Int, Identifiable, Hashable { |
||||
var id: Int { self.rawValue } |
||||
case prepare |
||||
} |
||||
|
||||
var groupStageView: some View { |
||||
ForEach(0..<(groupStage.size), id: \.self) { index in |
||||
// let entrant : Entrant? = runningGroupStageOrderedByScore ? groupStage.orderedByScore[Int(index)] : groupStage.entrantAtIndex(Int(index)) |
||||
// if let entrant { |
||||
// GroupStageEntrantMenuView(entrant: entrant, groupStage: groupStage, index: index.intValue) |
||||
// } else { |
||||
Menu { |
||||
// Section { |
||||
// EntrantPickerView(groupStage: groupStage, index: Int(index)) |
||||
// } |
||||
// |
||||
// if let tournament = groupStage.tournament, let deltaLabel = tournament.deltaLabel(index.intValue, groupStageIndex: groupStage.index.intValue) { |
||||
// let date = tournament.localizedDate ?? "" |
||||
// Divider() |
||||
// Section { |
||||
// ShareLink(item: "\(tournament.localizedTitle)\n\(date)\nCherche une équipe dont le poids d'équipe " + deltaLabel) { |
||||
// Text(deltaLabel) |
||||
// } |
||||
// } header: { |
||||
// Text("Remplacer avec un poids d'équipe") |
||||
// } |
||||
// } |
||||
} label: { |
||||
HStack { |
||||
Text("#\(index+1)") |
||||
Text("Aucune équipe") |
||||
} |
||||
} |
||||
// } |
||||
} |
||||
} |
||||
|
||||
var body: some View { |
||||
Section { |
||||
groupStageView |
||||
// .disabled(canUpdateTournament == false) |
||||
// .sheet(item: $selectedMenuLink) { selectedMenuLink in |
||||
// switch selectedMenuLink { |
||||
// case .prepare: |
||||
// PrepareGroupStageView(groupStage: groupStage) |
||||
// } |
||||
// } |
||||
} header: { |
||||
HStack { |
||||
if groupStage.isBroadcasted() { |
||||
Label(groupStage.title(), systemImage: "airplayvideo") |
||||
} else { |
||||
Text(groupStage.title()) |
||||
} |
||||
Spacer() |
||||
if let startDate = groupStage.startDate { |
||||
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute())) |
||||
} |
||||
} |
||||
} footer: { |
||||
HStack { |
||||
if groupStage.matches.isEmpty { |
||||
Button { |
||||
//groupStage.startGroupStage() |
||||
//save() |
||||
} label: { |
||||
Text("Créer les matchs") |
||||
} |
||||
.buttonStyle(.borderless) |
||||
} |
||||
Spacer() |
||||
Menu { |
||||
// Button { |
||||
// selectedMenuLink = .prepare |
||||
// } label: { |
||||
// Label("Préparer", systemImage: "calendar") |
||||
// } |
||||
// |
||||
// Menu { |
||||
// MenuWarnView(warningSender: groupStage) |
||||
// } label: { |
||||
// Label("Prévenir", systemImage: "person.crop.circle") |
||||
// } |
||||
// |
||||
// if groupStage.isBroadcasted() { |
||||
// Button { |
||||
// groupStage.refreshBroadcast() |
||||
// } label: { |
||||
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill") |
||||
// } |
||||
// Button { |
||||
// groupStage.stopBroadcast() |
||||
// save() |
||||
// } label: { |
||||
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill") |
||||
// } |
||||
// } else if groupStage.tournament?.canBroadcast() == true { |
||||
// Button { |
||||
// Task { |
||||
// try? await groupStage.broadcastGroupStage() |
||||
// save() |
||||
// } |
||||
// } label: { |
||||
// Label("Diffuser", systemImage: "airplayvideo") |
||||
// } |
||||
// } |
||||
// |
||||
// Divider() |
||||
// if groupStage.tournament?.canBroadcast() == true { |
||||
// Menu { |
||||
// Button { |
||||
// Task { |
||||
// try? await groupStage.broadcastGroupStageMatches() |
||||
// save() |
||||
// } |
||||
// } label: { |
||||
// Label("Diffuser", systemImage: "airplayvideo") |
||||
// } |
||||
// |
||||
// Button { |
||||
// groupStage.refreshBroadcastMatches() |
||||
// } label: { |
||||
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill") |
||||
// } |
||||
// Button { |
||||
// groupStage.stopBroadcastMatches() |
||||
// save() |
||||
// } label: { |
||||
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill") |
||||
// } |
||||
// } label: { |
||||
// Text("Diffusion des matchs") |
||||
// } |
||||
// } |
||||
// |
||||
// Divider() |
||||
// Menu { |
||||
// if groupStage.orderedMatches.isEmpty == false { |
||||
// Button(role: .destructive) { |
||||
// groupStage.startGroupStage() |
||||
// save() |
||||
// } label: { |
||||
// Label("Re-démarrer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash") |
||||
// } |
||||
// } |
||||
// |
||||
// if groupStage.orderedMatches.isEmpty == false { |
||||
// Button(role: .destructive) { |
||||
// groupStage.removeMatches() |
||||
// save() |
||||
// } label: { |
||||
// Label("Supprimer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash") |
||||
// } |
||||
// } |
||||
// |
||||
// Button(role: .destructive) { |
||||
// groupStage.tournament?.completeEntries.filter { $0.groupStagePosition == groupStage.index }.forEach { $0.resetGroupStagePosition() } |
||||
// groupStage.tournament?.removeFromGroupStages(groupStage) |
||||
// groupStage.tournament?.numberOfGroupStages -= 1 |
||||
// save() |
||||
// } label: { |
||||
// Label("Supprimer la \(groupStage.titleLabel.lowercased())", systemImage: "trash") |
||||
// } |
||||
// } label: { |
||||
// Text("Éditer") |
||||
// } |
||||
} label: { |
||||
HStack { |
||||
Spacer() |
||||
Label("Options", systemImage: "ellipsis.circle").labelStyle(.titleOnly) |
||||
} |
||||
} |
||||
.buttonStyle(.borderless) |
||||
} |
||||
} |
||||
// .onAppear { |
||||
// if let tournament = groupStage.tournament { |
||||
// canUpdateTournament = PersistenceController.shared.container.canUpdateRecord(forManagedObjectWith: tournament.objectID) |
||||
// } else { |
||||
// canUpdateTournament = true |
||||
// } |
||||
// } |
||||
} |
||||
|
||||
// func save() { |
||||
// do { |
||||
// groupStage.objectWillChange.send() |
||||
// groupStage.tournament?.orderedGroupStages.forEach { $0.objectWillChange.send() } |
||||
// groupStage.tournament?.objectWillChange.send() |
||||
// |
||||
// try viewContext.save() |
||||
// } catch { |
||||
// // Replace this implementation with code to handle the error appropriately. |
||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. |
||||
// let nsError = error as NSError |
||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
// } |
||||
// } |
||||
} |
||||
|
||||
|
||||
//struct GroupStageEntrantMenuView: View { |
||||
// @ObservedObject var entrant: Entrant |
||||
// @ObservedObject var groupStage: GroupStage |
||||
// @Environment(\.managedObjectContext) private var viewContext |
||||
// @AppStorage("showLongLabel") private var showLongLabel: Bool = false |
||||
// @AppStorage("hideRank") private var hideRank: Bool = false |
||||
// |
||||
// let index: Int |
||||
// |
||||
// var body: some View { |
||||
// Menu { |
||||
// ForEach(entrant.orderedPlayers) { player in |
||||
// Menu { |
||||
// Text(player.formattedRank) |
||||
// Text(player.localizedAge) |
||||
// if let computedClubName = player.computedClubName { |
||||
// Text(computedClubName) |
||||
// } |
||||
// } label: { |
||||
// Text(player.longLabel) |
||||
// } |
||||
// } |
||||
// |
||||
// if groupStage.tournament?.isOver == false { |
||||
// if entrant.qualified == false { |
||||
// Divider() |
||||
// Button { |
||||
// entrant.addToQualifiedGroup() |
||||
// entrant.objectWillChange.send() |
||||
// entrant.orderedGroupStages.forEach { $0.objectWillChange.send() } |
||||
// entrant.currentTournament?.objectWillChange.send() |
||||
// entrant.currentTournament?.orderedMatches.forEach { $0.objectWillChange.send() } |
||||
// save() |
||||
// } label: { |
||||
// Label("Qualifier l'équipe", systemImage: "checkmark") |
||||
// } |
||||
// } |
||||
// |
||||
// Divider() |
||||
// if entrant.qualified { |
||||
// Menu { |
||||
// Button(role: .destructive) { |
||||
// entrant.unqualified() |
||||
// entrant.objectWillChange.send() |
||||
// entrant.orderedGroupStages.forEach { $0.objectWillChange.send() } |
||||
// entrant.currentTournament?.objectWillChange.send() |
||||
// entrant.currentTournament?.orderedMatches.forEach { $0.objectWillChange.send() } |
||||
// save() |
||||
// } label: { |
||||
// Label("Annuler la qualification", systemImage: "xmark") |
||||
// } |
||||
// } label: { |
||||
// Text("Qualification") |
||||
// } |
||||
// } |
||||
// |
||||
// Menu { |
||||
// if let deltaLabel = groupStage.tournament?.deltaLabel(index, groupStageIndex: groupStage.index.intValue) { |
||||
// Section { |
||||
// Button(role: .destructive) { |
||||
// entrant.resetGroupStagePosition() |
||||
// save() |
||||
// } label: { |
||||
// Text(deltaLabel) |
||||
// } |
||||
// Divider() |
||||
// } header: { |
||||
// Text("Toute l'équipe, poids: " + entrant.updatedRank.formatted()) |
||||
// } |
||||
// } |
||||
// |
||||
// ForEach(entrant.orderedPlayers) { player in |
||||
// if let deltaLabel = groupStage.tournament?.playerDeltaLabel(rank: entrant.otherPlayer(player)?.currentRank, positionInGroupStage: index, groupStageIndex: groupStage.index.intValue) { |
||||
// Section { |
||||
// Button(role: .destructive) { |
||||
// entrant.team?.removeFromPlayers(player) |
||||
// save() |
||||
// } label: { |
||||
// Text(deltaLabel) |
||||
// } |
||||
// Divider() |
||||
// } header: { |
||||
// Text(player.longLabel + ", rang: " + player.formattedRank) |
||||
// } |
||||
// } |
||||
// } |
||||
// } label: { |
||||
// Text("Remplacement") |
||||
// } |
||||
// |
||||
// Menu { |
||||
// Button(role: .destructive) { |
||||
// entrant.resetGroupStagePosition() |
||||
// save() |
||||
// } label: { |
||||
// Label("Retirer l'équipe", systemImage: "xmark") |
||||
// } |
||||
// } label: { |
||||
// Text("Retirer") |
||||
// } |
||||
// |
||||
// } |
||||
// } label: { |
||||
// HStack(alignment: .center) { |
||||
// if let tournament = groupStage.tournament, groupStage.hasEnded, groupStage.groupStageRound > 0 { |
||||
// Text("#\(index + Int((groupStage.index - tournament.numberOfGroupStages)*tournament.teamsPerGroupStage) + 1)") |
||||
// } else { |
||||
// Text("#\(index + 1)") |
||||
// } |
||||
// VStack(alignment: .leading, spacing: 0) { |
||||
// if hideRank == false { |
||||
// Text("Poids \(entrant.updatedRank)") |
||||
// .font(.caption) |
||||
// } |
||||
// |
||||
// HStack { |
||||
// if let brand = entrant.team?.brand?.title { |
||||
// Text(brand) |
||||
// } else { |
||||
// |
||||
// VStack(alignment: .leading) { |
||||
// Text(entrant.longLabelPlayerOne) |
||||
// Text(entrant.longLabelPlayerTwo) |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// if groupStage.tournament?.isRoundSwissTournament() == true { |
||||
// if entrant.groupStagePosition == groupStage.index { |
||||
// Text("forcé") |
||||
// } else { |
||||
// Text("auto") |
||||
// } |
||||
// } else { |
||||
// if entrant.qualified { |
||||
// Image(systemName: "checkmark.seal") |
||||
// } |
||||
// } |
||||
// } |
||||
// } |
||||
// Spacer() |
||||
// Text(groupStage.scoreLabel(for: entrant.position(in: groupStage))) |
||||
// } |
||||
// } |
||||
// .buttonStyle(.plain) |
||||
// } |
||||
// |
||||
// func save() { |
||||
// do { |
||||
// try viewContext.save() |
||||
// } catch { |
||||
// // Replace this implementation with code to handle the error appropriately. |
||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. |
||||
// let nsError = error as NSError |
||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
// } |
||||
// } |
||||
//} |
||||
@ -0,0 +1,304 @@ |
||||
// |
||||
// GroupStagesView.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 11/12/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct GroupStagesView: View { |
||||
@Environment(Tournament.self) var tournament: Tournament |
||||
@State private var selectedGroupStageIndex: Int = -1 |
||||
@State private var startAllGroupStageConfirmation: Bool = false |
||||
@State private var confirmGroupStageRebuild: Bool = false |
||||
|
||||
func displayGroupStage(_ groupStage: GroupStage) -> Bool { |
||||
selectedGroupStageIndex == groupStage.index || selectedGroupStageIndex == -1 |
||||
} |
||||
|
||||
var dynamicTitle: String { |
||||
switch selectedGroupStageIndex { |
||||
case -1: |
||||
return "Toutes les poules" |
||||
default: |
||||
return tournament.groupStages[selectedGroupStageIndex].title() |
||||
} |
||||
} |
||||
// |
||||
// init(tournament: Tournament) { |
||||
// _tournament = ObservedObject(wrappedValue: tournament) |
||||
// if let index = tournament.orderedGroupStages.firstIndex(where: { $0.isRunning }) { |
||||
// _selectedGroupStageIndex = State(wrappedValue: index.int64Value + 1) |
||||
// } |
||||
// } |
||||
|
||||
var body: some View { |
||||
List { |
||||
if tournament.missingQualifiedFromGroupStages().isEmpty == false && tournament.qualifiedTeams().count >= tournament.qualifiedFromGroupStage() && tournament.groupStageAdditionalQualified > 0 { |
||||
NavigationLink { |
||||
//DrawView(tournament: tournament) |
||||
} label: { |
||||
LabeledContent { |
||||
Text(tournament.moreQualifiedToDraw().formatted() + "/" + tournament.groupStageAdditionalQualified.formatted()) |
||||
} label: { |
||||
Text("Qualifié\(tournament.groupStageAdditionalQualified.pluralSuffix) supplémentaire\(tournament.groupStageAdditionalQualified.pluralSuffix)") |
||||
let message = [tournament.groupStageAdditionalQualified.formatted(), " meilleur", tournament.groupStageAdditionalQualified.pluralSuffix, " ", (tournament.qualifiedPerGroupStage + 1).ordinalFormatted()].joined() |
||||
Text(message) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) { |
||||
// Section { |
||||
// RowButtonView(title: "Reconstruire les poules") { |
||||
// confirmGroupStageRebuild = true |
||||
// } |
||||
// .modify { |
||||
// if UIDevice.current.userInterfaceIdiom == .pad { |
||||
// $0.alert("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) { |
||||
// |
||||
// Button(role: .destructive) { |
||||
// tournament.refreshGroupStages() |
||||
// save() |
||||
// } label: { |
||||
// Text("Reconstruire") |
||||
// } |
||||
// |
||||
// |
||||
// Button(role: .cancel) { |
||||
// |
||||
// } label: { |
||||
// Text("Annuler") |
||||
// } |
||||
// } message: { |
||||
// Text("Attention, cela peut modifier les poules existants.") |
||||
// |
||||
// } |
||||
// } else { |
||||
// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) { |
||||
// Button(role: .destructive) { |
||||
// tournament.refreshGroupStages() |
||||
// save() |
||||
// } label: { |
||||
// Text("Reconstruire") |
||||
// } |
||||
// } message: { |
||||
// Text("Attention, cela peut modifier les poules existants.") |
||||
// } |
||||
// } |
||||
// } |
||||
// } header: { |
||||
// Text("Erreur détectée") |
||||
// } |
||||
// } |
||||
// |
||||
|
||||
// if tournament.isRoundSwissTournament() == false && (tournament.orderedGroupStages.allSatisfy({ $0.orderedMatches.count > 0 }) == false && tournament.groupStagesAreOver == false && tournament.groupStagesCount > 0) { |
||||
// Section { |
||||
// RowButtonView(title: "Générer les matchs de poules") { |
||||
// startAllGroupStageConfirmation = true |
||||
// } |
||||
// .modify { |
||||
// if UIDevice.current.userInterfaceIdiom == .pad { |
||||
// $0.alert("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) { |
||||
// Button("Générer") { |
||||
// tournament.orderedGroupStages.forEach { |
||||
// if $0.orderedMatches.isEmpty { |
||||
// $0.startGroupStage() |
||||
// } |
||||
// } |
||||
// save() |
||||
// } |
||||
// Button(role: .cancel) { |
||||
// |
||||
// } label: { |
||||
// Text("Annuler") |
||||
// } |
||||
// } |
||||
// |
||||
// } else { |
||||
// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) { |
||||
// Button("Générer") { |
||||
// tournament.orderedGroupStages.forEach { |
||||
// if $0.orderedMatches.isEmpty { |
||||
// $0.startGroupStage() |
||||
// } |
||||
// } |
||||
// save() |
||||
// } |
||||
// } |
||||
// } |
||||
// } |
||||
// } |
||||
// } |
||||
// |
||||
if tournament.groupStagesAreOver() == false { |
||||
// Section { |
||||
// GroupStageMatchAvailableToStartView(tournament: tournament, groupStageIndex: selectedGroupStageIndex) |
||||
// } header: { |
||||
// if selectedGroupStageIndex == -1 { |
||||
// Text("Matchs de poules prêt à démarrer") |
||||
// } else { |
||||
// Text("Matchs de la poule \(selectedGroupStageIndex) prêt à démarrer") |
||||
// } |
||||
// } footer: { |
||||
// Text("présence d'au moins 2 équipes d'une même poule ayant réglé.") |
||||
// } |
||||
} |
||||
|
||||
// if tournament.teamsPerGroupStage == 3 && tournament.qualifiedPerGroupStage == 1 && tournament.numberOfGroupStages%2 == 0 && tournament.moreQualifiedFromGroupStages == 0 { |
||||
// Section { |
||||
// NavigationLink { |
||||
// GroupStageMissingMatchView(tournament: tournament) |
||||
// } label: { |
||||
// Text("Matchs de classement de poules") |
||||
// } |
||||
// } |
||||
// } |
||||
|
||||
ForEach(tournament.groupStages) { groupStage in |
||||
if displayGroupStage(groupStage) && groupStage.hasEnded() == false { |
||||
GroupStageView(groupStage: groupStage) |
||||
if groupStage.matches.isEmpty == false { |
||||
Section { |
||||
ForEach(groupStage.matches) { match in |
||||
MatchRowView(setupSeedContext: false, matchViewStyle: .sectionedStandardStyle) |
||||
.environment(match) |
||||
} |
||||
} header: { |
||||
Text("Matchs de la " + groupStage.title()) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.toolbar { |
||||
ToolbarItem(placement: .principal) { |
||||
if tournament.groupStages.count < 6 { |
||||
Picker(selection: $selectedGroupStageIndex) { |
||||
Text("Toutes").tag(-1) |
||||
ForEach(tournament.groupStages) { groupStage in |
||||
Text(groupStage.title(.short)).tag(groupStage.index) |
||||
} |
||||
} label: { |
||||
|
||||
} |
||||
.labelsHidden() |
||||
.pickerStyle(.segmented) |
||||
} else if tournament.groupStages.count < 8 { |
||||
Picker(selection: $selectedGroupStageIndex) { |
||||
Image(systemName: "square.stack").tag(-1) |
||||
ForEach(tournament.groupStages) { groupStage in |
||||
Text(groupStage.title(.short)).tag(groupStage.index) |
||||
} |
||||
} label: { |
||||
|
||||
} |
||||
.labelsHidden() |
||||
.pickerStyle(.segmented) |
||||
} else { |
||||
Picker(selection: $selectedGroupStageIndex) { |
||||
Text("Voir toutes les poules").tag(-1) |
||||
ForEach(tournament.groupStages) { groupStage in |
||||
Text(groupStage.title()).tag(groupStage.index) |
||||
} |
||||
} label: { |
||||
Text("\(tournament.groupStages.count.formatted()) poules") |
||||
} |
||||
} |
||||
} |
||||
|
||||
ToolbarItem(placement: .topBarTrailing) { |
||||
Menu { |
||||
// menuAddGroupStage |
||||
// menuBuildAllGroupStages |
||||
// menuGenerateGroupStage(.random) |
||||
// menuGenerateGroupStage(.snake) |
||||
// menuGenerateGroupStage(.swiss) |
||||
} label: { |
||||
LabelOptions() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// |
||||
// var menuBuildAllGroupStages: some View { |
||||
// Button(role: .destructive) { |
||||
// tournament.orderedEntries.forEach { entrant in |
||||
// if entrant.groupStagePosition > 0 { |
||||
// entrant.resetGroupStagePosition() |
||||
// } |
||||
// } |
||||
// tournament.buildGroupStages() |
||||
// do { |
||||
// try viewContext.save() |
||||
// } catch { |
||||
// // Replace this implementation with code to handle the error appropriately. |
||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. |
||||
// let nsError = error as NSError |
||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
// } |
||||
// |
||||
// } label: { |
||||
// Label("Refaire les poules", systemImage: "restart") |
||||
// } |
||||
// } |
||||
// |
||||
// @ViewBuilder |
||||
// func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View { |
||||
// Button(role: .destructive) { |
||||
// tournament.stopBroadcastGroupStages() |
||||
// tournament.groupStageOrderingMode = mode |
||||
// tournament.refreshGroupStages() |
||||
// save() |
||||
// } label: { |
||||
// Label("Poule \(mode.localizedLabel.lowercased())", systemImage: mode.systemImage) |
||||
// } |
||||
// } |
||||
// |
||||
// func addGroupStage(_ size: Int64) { |
||||
// let groupStage = GroupStage(context: viewContext) |
||||
// groupStage.index = tournament.firstIndexToUseForNewGroupStage |
||||
// groupStage.size = Int64(size) |
||||
// groupStage.matchFormatRawValue = tournament.groupStageMatchFormatRawValue |
||||
// print("addGroupStage groupStagesCount", tournament.groupStagesCount) |
||||
// print("addGroupStage numberOfGroupStages", tournament.numberOfGroupStages) |
||||
// if tournament.groupStagesCount >= tournament.numberOfGroupStages { |
||||
// tournament.numberOfGroupStages += 1 |
||||
// } |
||||
// tournament.addToGroupStages(groupStage) |
||||
// save() |
||||
// } |
||||
// |
||||
// var menuAddGroupStage: some View { |
||||
// Menu { |
||||
// ForEach(-1...1) { index in |
||||
// let i = tournament.teamsPerGroupStage + Int64(index) |
||||
// Button { |
||||
// addGroupStage(i) |
||||
// } label: { |
||||
// Text("Poule de \(i)") |
||||
// } |
||||
// .disabled(i < 2) |
||||
// } |
||||
// } label: { |
||||
// Label("Ajouter une poule", systemImage: "server.rack") |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// |
||||
// func save() { |
||||
// do { |
||||
// tournament.objectWillChange.send() |
||||
// try viewContext.save() |
||||
// viewContext.refreshAllObjects() |
||||
// } catch { |
||||
// // Replace this implementation with code to handle the error appropriately. |
||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. |
||||
// let nsError = error as NSError |
||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
// } |
||||
// } |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
// |
||||
// MatchDetailView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct MatchDetailView: View { |
||||
@Environment(Match.self) var match: Match |
||||
let setupSeedContext: Bool |
||||
let matchViewStyle: MatchViewStyle |
||||
|
||||
var body: some View { |
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
MatchDetailView(setupSeedContext: false, matchViewStyle: .standardStyle) |
||||
.environment(Match.mock()) |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
// |
||||
// MatchRowView.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 25/11/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct MatchRowView: View { |
||||
@Environment(Match.self) var match: Match |
||||
let setupSeedContext: Bool |
||||
let matchViewStyle: MatchViewStyle |
||||
|
||||
@ViewBuilder |
||||
var body: some View { |
||||
if setupSeedContext { |
||||
MatchSummaryView(setupSeedContext: setupSeedContext, matchViewStyle: matchViewStyle) |
||||
} else { |
||||
NavigationLink { |
||||
MatchDetailView(setupSeedContext: setupSeedContext, matchViewStyle: matchViewStyle) |
||||
.environment(match) |
||||
} label: { |
||||
MatchSummaryView(setupSeedContext: setupSeedContext, matchViewStyle: matchViewStyle) |
||||
} |
||||
//.modifier(BroadcastViewModifier(isBroadcasted: match.isBroadcasted())) |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
#Preview { |
||||
MatchRowView(setupSeedContext: false, matchViewStyle: .standardStyle) |
||||
.environment(Match.mock()) |
||||
} |
||||
@ -0,0 +1,24 @@ |
||||
// |
||||
// MatchSummaryView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct MatchSummaryView: View { |
||||
@Environment(Match.self) var match: Match |
||||
let setupSeedContext: Bool |
||||
let matchViewStyle: MatchViewStyle |
||||
|
||||
|
||||
var body: some View { |
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
MatchSummaryView(setupSeedContext: false, matchViewStyle: .standardStyle) |
||||
.environment(Match.mock()) |
||||
} |
||||
@ -0,0 +1,30 @@ |
||||
// |
||||
// TournamentRunningView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct TournamentRunningView: View { |
||||
@Environment(Tournament.self) private var tournament: Tournament |
||||
|
||||
@ViewBuilder |
||||
var body: some View { |
||||
Section { |
||||
NavigationLink(value: Screen.groupStage) { |
||||
LabeledContent { |
||||
Text(tournament.groupStageStatus()) |
||||
} label: { |
||||
Text("Poules") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
TournamentRunningView() |
||||
.environment(Tournament.mock()) |
||||
} |
||||
Loading…
Reference in new issue