|
|
|
|
@ -6,6 +6,7 @@ |
|
|
|
|
// |
|
|
|
|
|
|
|
|
|
import SwiftUI |
|
|
|
|
import LeStorage |
|
|
|
|
|
|
|
|
|
struct GroupStageView: View { |
|
|
|
|
@EnvironmentObject var dataStore: DataStore |
|
|
|
|
@ -15,21 +16,7 @@ struct GroupStageView: View { |
|
|
|
|
@State private var confirmRemoveAll: Bool = false |
|
|
|
|
@State private var confirmResetMatch: Bool = false |
|
|
|
|
@State private var groupStageName: String = "" |
|
|
|
|
|
|
|
|
|
private enum GroupStageSortingMode { |
|
|
|
|
case auto |
|
|
|
|
case score |
|
|
|
|
case weight |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var sortByScore: Bool { |
|
|
|
|
sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func teamAt(atIndex index: Int) -> TeamRegistration? { |
|
|
|
|
sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init(groupStage: GroupStage) { |
|
|
|
|
self.groupStage = groupStage |
|
|
|
|
_groupStageName = State(wrappedValue: groupStage.groupStageTitle()) |
|
|
|
|
@ -38,7 +25,7 @@ struct GroupStageView: View { |
|
|
|
|
var body: some View { |
|
|
|
|
List { |
|
|
|
|
Section { |
|
|
|
|
_groupStageView() |
|
|
|
|
GroupStageScoreView(groupStage: groupStage, sortByScore: _sortByScore) |
|
|
|
|
} header: { |
|
|
|
|
if let startDate = groupStage.startDate { |
|
|
|
|
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formattedAsHourMinute()) |
|
|
|
|
@ -47,19 +34,9 @@ struct GroupStageView: View { |
|
|
|
|
HStack { |
|
|
|
|
Spacer() |
|
|
|
|
Button { |
|
|
|
|
if sortingMode == .auto { |
|
|
|
|
if groupStage.hasEnded() { |
|
|
|
|
sortingMode = .weight |
|
|
|
|
} else { |
|
|
|
|
sortingMode = .score |
|
|
|
|
} |
|
|
|
|
} else if sortingMode == .weight { |
|
|
|
|
sortingMode = .score |
|
|
|
|
} else { |
|
|
|
|
sortingMode = .weight |
|
|
|
|
} |
|
|
|
|
_updateSortingMode() |
|
|
|
|
} label: { |
|
|
|
|
Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly) |
|
|
|
|
Label(_sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly) |
|
|
|
|
.underline() |
|
|
|
|
} |
|
|
|
|
.buttonStyle(.borderless) |
|
|
|
|
@ -70,11 +47,11 @@ struct GroupStageView: View { |
|
|
|
|
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()).id(UUID()) |
|
|
|
|
MatchListView(section: "terminés", matches: groupStage.finishedMatches(), isExpanded: false).id(UUID()) |
|
|
|
|
} |
|
|
|
|
.onChange(of: groupStageName) { |
|
|
|
|
groupStage.name = groupStageName |
|
|
|
|
try? dataStore.groupStages.addOrUpdate(instance: groupStage) |
|
|
|
|
_save() |
|
|
|
|
} |
|
|
|
|
.toolbar { |
|
|
|
|
ToolbarItem(placement: .topBarTrailing) { |
|
|
|
|
@ -84,65 +61,111 @@ struct GroupStageView: View { |
|
|
|
|
.navigationTitle($groupStageName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _groupStageView() -> some View { |
|
|
|
|
ForEach(0..<(groupStage.size), id: \.self) { index in |
|
|
|
|
if let team = teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition { |
|
|
|
|
NavigationLink { |
|
|
|
|
GroupStageTeamView(groupStage: groupStage, team: team) |
|
|
|
|
} label: { |
|
|
|
|
HStack(alignment: .center) { |
|
|
|
|
private enum GroupStageSortingMode { |
|
|
|
|
case auto |
|
|
|
|
case score |
|
|
|
|
case weight |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private var _sortByScore: Bool { |
|
|
|
|
sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _updateSortingMode() { |
|
|
|
|
if sortingMode == .auto { |
|
|
|
|
if groupStage.hasEnded() { |
|
|
|
|
sortingMode = .weight |
|
|
|
|
} else { |
|
|
|
|
sortingMode = .score |
|
|
|
|
} |
|
|
|
|
} else if sortingMode == .weight { |
|
|
|
|
sortingMode = .score |
|
|
|
|
} else { |
|
|
|
|
sortingMode = .weight |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct GroupStageScoreView: View { |
|
|
|
|
@EnvironmentObject var dataStore: DataStore |
|
|
|
|
let groupStage: GroupStage |
|
|
|
|
let sortByScore: Bool |
|
|
|
|
let teams: [TeamRegistration] |
|
|
|
|
|
|
|
|
|
init(groupStage: GroupStage, sortByScore: Bool) { |
|
|
|
|
self.groupStage = groupStage |
|
|
|
|
self.sortByScore = sortByScore |
|
|
|
|
self.teams = groupStage.teams(sortByScore) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _teamAt(atIndex index: Int) -> TeamRegistration? { |
|
|
|
|
sortByScore ? teams[safe: index] : groupStage.teamAt(groupStagePosition: index) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
ForEach(0..<(groupStage.size), id: \.self) { index in |
|
|
|
|
if let team = _teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition { |
|
|
|
|
NavigationLink { |
|
|
|
|
GroupStageTeamView(groupStage: groupStage, team: team) |
|
|
|
|
} label: { |
|
|
|
|
VStack(alignment: .leading, spacing: 4.0) { |
|
|
|
|
HStack(spacing: 6.0) { |
|
|
|
|
Text("#\(groupStagePosition + 1)") |
|
|
|
|
Text("Poids \(team.weight)") |
|
|
|
|
Spacer() |
|
|
|
|
if team.qualified { |
|
|
|
|
Text("qualifié") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.font(.footnote) |
|
|
|
|
HStack { |
|
|
|
|
if let teamName = team.name { |
|
|
|
|
Text(teamName) |
|
|
|
|
Text(teamName).font(.title) |
|
|
|
|
} else { |
|
|
|
|
VStack(alignment: .leading) { |
|
|
|
|
ForEach(team.players()) { player in |
|
|
|
|
Text(player.playerLabel()) |
|
|
|
|
Text(player.playerLabel()).lineLimit(1) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if team.qualified { |
|
|
|
|
Image(systemName: "checkmark.seal") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Spacer() |
|
|
|
|
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { |
|
|
|
|
VStack(alignment: .center) { |
|
|
|
|
HStack(spacing: 0.0) { |
|
|
|
|
Text(score.wins) |
|
|
|
|
Text("/") |
|
|
|
|
Text(score.losses) |
|
|
|
|
}.bold() |
|
|
|
|
if let setsDifference = score.setsDifference { |
|
|
|
|
Text(setsDifference + " sets") |
|
|
|
|
} |
|
|
|
|
if let gamesDifference = score.gamesDifference { |
|
|
|
|
Text(gamesDifference + " jeux") |
|
|
|
|
Spacer() |
|
|
|
|
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { |
|
|
|
|
VStack(alignment: .trailing) { |
|
|
|
|
HStack(spacing: 0.0) { |
|
|
|
|
Text(score.wins) |
|
|
|
|
Text("/") |
|
|
|
|
Text(score.losses) |
|
|
|
|
}.font(.headline).monospacedDigit() |
|
|
|
|
if let setsDifference = score.setsDifference { |
|
|
|
|
HStack(spacing: 4.0) { |
|
|
|
|
Text(setsDifference) |
|
|
|
|
Text("sets") |
|
|
|
|
}.font(.footnote) |
|
|
|
|
} |
|
|
|
|
if let gamesDifference = score.gamesDifference { |
|
|
|
|
HStack(spacing: 4.0) { |
|
|
|
|
Text(gamesDifference) |
|
|
|
|
Text("jeux") |
|
|
|
|
}.font(.footnote) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
HStack(alignment: .center) { |
|
|
|
|
.listRowView(isActive: team.qualified, color: .master) |
|
|
|
|
} else { |
|
|
|
|
VStack(alignment: .leading, spacing: 0) { |
|
|
|
|
HStack { |
|
|
|
|
Text("#\(index + 1)") |
|
|
|
|
} |
|
|
|
|
.font(.caption) |
|
|
|
|
Text("#\(index + 1)") |
|
|
|
|
.font(.caption) |
|
|
|
|
TeamPickerView(teamPicked: { team in |
|
|
|
|
print(team.pasteData()) |
|
|
|
|
team.groupStage = groupStage.id |
|
|
|
|
team.groupStagePosition = index |
|
|
|
|
try? dataStore.teamRegistrations.addOrUpdate(instance: team) |
|
|
|
|
do { |
|
|
|
|
try dataStore.teamRegistrations.addOrUpdate(instance: team) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -156,121 +179,15 @@ struct GroupStageView: View { |
|
|
|
|
Button("Retirer le nom") { |
|
|
|
|
groupStage.name = nil |
|
|
|
|
groupStageName = groupStage.groupStageTitle() |
|
|
|
|
try? dataStore.groupStages.addOrUpdate(instance: groupStage) |
|
|
|
|
_save() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// if groupStage.playedMatches().isEmpty { |
|
|
|
|
// Button { |
|
|
|
|
// //groupStage.startGroupStage() |
|
|
|
|
// //save() |
|
|
|
|
// } label: { |
|
|
|
|
// Text("Créer les matchs") |
|
|
|
|
// } |
|
|
|
|
// .buttonStyle(.borderless) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
Button("Retirer tout le monde", role: .destructive) { |
|
|
|
|
confirmRemoveAll = true |
|
|
|
|
} |
|
|
|
|
Button("Recommencer tous les matchs", role: .destructive) { |
|
|
|
|
confirmResetMatch = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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: { |
|
|
|
|
LabelOptions() |
|
|
|
|
} |
|
|
|
|
@ -281,7 +198,11 @@ struct GroupStageView: View { |
|
|
|
|
team.groupStagePosition = nil |
|
|
|
|
team.groupStage = nil |
|
|
|
|
} |
|
|
|
|
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: teams) |
|
|
|
|
do { |
|
|
|
|
try dataStore.teamRegistrations.addOrUpdate(contentOfs: teams) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) { |
|
|
|
|
@ -292,18 +213,11 @@ struct GroupStageView: View { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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)") |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
private func _save() { |
|
|
|
|
do { |
|
|
|
|
try dataStore.groupStages.addOrUpdate(instance: groupStage) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|