club_update
Razmig Sarkissian 1 year ago
parent b459b33ec5
commit b594101292
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/DataStore.swift
  3. 16
      PadelClub/Data/Match.swift
  4. 17
      PadelClub/Data/Tournament.swift
  5. 27
      PadelClub/Views/Components/CopyPasteButtonView.swift
  6. 8
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  7. 39
      PadelClub/Views/GroupStage/GroupStageView.swift
  8. 51
      PadelClub/Views/GroupStage/GroupStagesSettingsView.swift
  9. 19
      PadelClub/Views/GroupStage/GroupStagesView.swift
  10. 11
      PadelClub/Views/Match/Components/MatchDateView.swift
  11. 23
      PadelClub/Views/Match/EditSharingView.swift
  12. 11
      PadelClub/Views/Match/MatchDetailView.swift
  13. 10
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  14. 36
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  15. 27
      PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift
  16. 13
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  17. 9
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  18. 24
      PadelClub/Views/Player/PlayerDetailView.swift
  19. 13
      PadelClub/Views/Round/RoundSettingsView.swift
  20. 225
      PadelClub/Views/Team/EditingTeamView.swift
  21. 195
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  22. 6
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  23. 12
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift

@ -230,6 +230,7 @@
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; };
FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; };
FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; };
FFCB74172C480411008384D0 /* CopyPasteButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */; };
FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; };
FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; };
FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; };
@ -569,6 +570,7 @@
FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; };
FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = "<group>"; };
FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; };
FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPasteButtonView.swift; sourceTree = "<group>"; };
FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamsCallingView.swift; sourceTree = "<group>"; };
FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = "<group>"; };
FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDatePicker.swift; sourceTree = "<group>"; };
@ -810,6 +812,7 @@
C4FC2E262C2AABC90021F3BF /* PasswordField.swift */,
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */,
C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */,
FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -1731,6 +1734,7 @@
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FFCB74172C480411008384D0 /* CopyPasteButtonView.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF1F4B712BF9EFE9000B4573 /* TournamentInscriptionView.swift in Sources */,
FF9267FF2BCE94830080F940 /* CallSettingsView.swift in Sources */,

@ -263,7 +263,7 @@ class DataStore: ObservableObject {
var runningMatches: [Match] = []
for tournament in lastTournaments {
let matches = tournament.tournamentStore.matches.filter { match in
match.confirmed && match.startDate != nil && match.endDate == nil && match.courtIndex != nil }
match.confirmed && match.startDate != nil && match.endDate == nil }
runningMatches.append(contentsOf: matches)
}
return runningMatches

@ -66,6 +66,10 @@ final class Match: ModelObject, Storable {
fatalError("missing store for \(String(describing: type(of: self)))")
}
var courtIndexForSorting: Int {
courtIndex ?? Int.max
}
// MARK: - Computed dependencies
var teamScores: [TeamScore] {
@ -474,8 +478,16 @@ defer {
if startDate == nil {
startDate = endDate?.addingTimeInterval(Double(-getDuration()*60))
}
winningTeamId = team(matchDescriptor.winner)?.id
losingTeamId = team(matchDescriptor.winner.otherTeam)?.id
let teamOne = team(matchDescriptor.winner)
let teamTwo = team(matchDescriptor.winner.otherTeam)
teamOne?.hasArrived()
teamTwo?.hasArrived()
winningTeamId = teamOne?.id
losingTeamId = teamTwo?.id
groupStageObject?.updateGroupStageState()
roundObject?.updateTournamentState()
updateFollowingMatchTeamScore()

@ -1104,7 +1104,7 @@ defer {
// return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
}
func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] {
func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) -> [Match] {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
@ -1115,17 +1115,6 @@ defer {
return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting)
}
func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting)
}
func runningMatches(_ allMatches: [Match]) -> [Match] {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
@ -1136,8 +1125,8 @@ defer {
#endif
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting)
}
func readyMatches(_ allMatches: [Match]) async -> [Match] {
func readyMatches(_ allMatches: [Match]) -> [Match] {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {

@ -0,0 +1,27 @@
//
// CopyPasteButtonView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/07/2024.
//
import SwiftUI
struct CopyPasteButtonView: View {
let pasteValue: String?
@State private var copied: Bool = false
var body: some View {
Button {
let pasteboard = UIPasteboard.general
pasteboard.string = pasteValue
} label: {
HStack(spacing: 0) {
Label(copied ? "copié" : "copier", systemImage: "doc.on.doc")
if copied == true {
Image(systemName: "checkmark")
}
}
}
}
}

@ -107,16 +107,16 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
}
.onAppear {
if let selectedDestination {
proxy.scrollTo(selectedDestination.id, anchor: .trailing)
proxy.scrollTo(selectedDestination.id, anchor: .center)
} else {
proxy.scrollTo("settings", anchor: .leading)
proxy.scrollTo("settings", anchor: .center)
}
}
.onChange(of: selectedDestination) {
if let selectedDestination {
proxy.scrollTo(selectedDestination.id, anchor: .trailing)
proxy.scrollTo(selectedDestination.id, anchor: .center)
} else {
proxy.scrollTo("settings", anchor: .leading)
proxy.scrollTo("settings", anchor: .center)
}
}
}

@ -15,12 +15,13 @@ struct GroupStageView: View {
@Environment(Tournament.self) private var tournament
@Bindable var groupStage: GroupStage
@State private var confirmGroupStageStart: Bool = false
@State private var sortingMode: GroupStageSortingMode = .auto
@State private var sortingMode: GroupStageSortingMode
let playedMatches: [Match]
init(groupStage: GroupStage) {
self.groupStage = groupStage
self.playedMatches = groupStage.playedMatches()
_sortingMode = .init(wrappedValue: groupStage.hasEnded() ? .score : .weight)
}
var tournamentStore: TournamentStore {
@ -30,7 +31,7 @@ struct GroupStageView: View {
var body: some View {
List {
Section {
GroupStageScoreView(groupStage: groupStage, sortByScore: _sortByScore)
GroupStageScoreView(groupStage: groupStage, sortByScore: sortingMode == .score)
} header: {
if let startDate = groupStage.startDate {
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formattedAsHourMinute())
@ -38,13 +39,14 @@ struct GroupStageView: View {
} footer: {
HStack {
Spacer()
Button {
_updateSortingMode()
Picker(selection: $sortingMode) {
Text("tri par score").tag(GroupStageSortingMode.score)
Text("tri par poids").tag(GroupStageSortingMode.weight)
} label: {
Label(_sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly)
.underline()
}
.buttonStyle(.borderless)
.pickerStyle(.segmented)
Spacer()
}
}
.headerProminence(.increased)
@ -66,29 +68,10 @@ struct GroupStageView: View {
}
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
@ -142,7 +125,7 @@ struct GroupStageView: View {
Text(player.playerLabel()).lineLimit(1)
.overlay {
if player.hasArrived && team.isHere() == false {
Color.logoYellow.opacity(0.6)
Color.green.opacity(0.6)
}
}
}

@ -20,27 +20,29 @@ struct GroupStagesSettingsView: View {
var body: some View {
List {
let missingQualifiedFromGroupStages = tournament.missingQualifiedFromGroupStages()
Section {
let name = "\((tournament.groupStageAdditionalQualified + 1).ordinalFormatted())"
NavigationLink("Tirage au sort d'un \(name)") {
SpinDrawView(drawees: ["Qualification d'un \(name)"], segments: missingQualifiedFromGroupStages) { results in
results.forEach { drawResult in
missingQualifiedFromGroupStages[drawResult.drawIndex].qualified = true
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex])
} catch {
Logger.error(error)
if tournament.groupStageAdditionalQualified > 0 {
let missingQualifiedFromGroupStages = tournament.missingQualifiedFromGroupStages()
Section {
let name = "\((tournament.groupStageAdditionalQualified + 1).ordinalFormatted())"
NavigationLink("Tirage au sort d'un \(name)") {
SpinDrawView(drawees: ["Qualification d'un \(name)"], segments: missingQualifiedFromGroupStages) { results in
results.forEach { drawResult in
missingQualifiedFromGroupStages[drawResult.drawIndex].qualified = true
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(instance: missingQualifiedFromGroupStages[drawResult.drawIndex])
} catch {
Logger.error(error)
}
}
}
}
}
.disabled(tournament.moreQualifiedToDraw() == 0 || missingQualifiedFromGroupStages.isEmpty)
} footer: {
if tournament.moreQualifiedToDraw() == 0 {
Text("Aucune équipe supplémentaire à qualifier. Vous pouvez en rajouter en modifier le paramètre dans structure.")
} else if missingQualifiedFromGroupStages.isEmpty {
Text("Aucune équipe supplémentaire à tirer au sort. Attendez la fin des poules.")
.disabled(tournament.moreQualifiedToDraw() == 0 || missingQualifiedFromGroupStages.isEmpty)
} footer: {
if tournament.moreQualifiedToDraw() == 0 {
Text("Aucune équipe supplémentaire à qualifier. Vous pouvez en rajouter en modifier le paramètre dans structure.")
} else if missingQualifiedFromGroupStages.isEmpty {
Text("Aucune équipe supplémentaire à tirer au sort. Attendez la fin des poules.")
}
}
}
@ -96,13 +98,7 @@ struct GroupStagesSettingsView: View {
Text("Suite à un changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.")
}
}
Section {
ShareLink(item: tournament.groupStages().compactMap { $0.pasteData() }.joined(separator: "\n\n")) {
Text("Exportez l'état poules")
}
}
#if DEBUG
Section {
RowButtonView("delete all group stages") {
@ -192,6 +188,11 @@ struct GroupStagesSettingsView: View {
.deferredRendering(for: .seconds(2))
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: tournament.groupStages().compactMap { $0.pasteData() }.joined(separator: "\n\n"))
}
}
}

@ -60,11 +60,12 @@ struct GroupStagesView: View {
}
}
let allMatches: [Match]
var allMatches: [Match] {
tournament.groupStagesMatches()
}
init(tournament: Tournament) {
self.tournament = tournament
self.allMatches = tournament.groupStagesMatches()
if tournament.shouldVerifyGroupStage {
_selectedDestination = State(wrappedValue: nil)
@ -85,10 +86,6 @@ struct GroupStagesView: View {
return allDestinations
}
@State private var runningMatches: [Match]?
@State private var readyMatches: [Match]?
@State private var availableToStart: [Match]?
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
@ -97,16 +94,12 @@ struct GroupStagesView: View {
let finishedMatches = tournament.finishedMatches(allMatches)
List {
let runningMatches = tournament.runningMatches(allMatches)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "prêt à démarrer", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "prêt à démarrer", matches: tournament.availableToStart(allMatches, in: runningMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false)
}
.task {
runningMatches = await tournament.asyncRunningMatches(allMatches)
availableToStart = await tournament.availableToStart(allMatches, in: runningMatches ?? [])
readyMatches = await tournament.readyMatches(allMatches)
}
.navigationTitle("Toutes les poules")
case .groupStage(let groupStage):
GroupStageView(groupStage: groupStage).id(groupStage.id)

@ -144,6 +144,17 @@ struct MatchDateView: View {
}
private func _save() {
if let startDate = match.startDate, let tournament = match.currentTournament() {
if startDate < tournament.startDate {
tournament.startDate = startDate
}
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
do {
try self.match.tournamentStore.matches.addOrUpdate(instance: match)
} catch {

@ -59,7 +59,6 @@ struct EditSharingView: View {
var body: some View {
List {
if let newImage {
Section {
let tip = SharePictureTip()
@ -87,12 +86,11 @@ struct EditSharingView: View {
}
}
} else {
Button {
showCamera = true
} label: {
Label("Prendre une photo", systemImage: "camera")
}
Section {
RowButtonView("Prendre une photo", systemImage: "camera") {
showCamera = true
}
}
}
Section {
@ -114,16 +112,7 @@ struct EditSharingView: View {
} footer: {
HStack {
Spacer()
Button {
UIPasteboard.general.string = shareMessage
copied = true
} label: {
Label(copied ? "copié" : "copier", systemImage: "doc.on.doc")
}
if shareMessage == UIPasteboard.general.string || copied == true {
Image(systemName: "checkmark")
}
CopyPasteButtonView(pasteValue: shareMessage)
}
}
}

@ -491,6 +491,17 @@ struct MatchDetailView: View {
}
private func save() {
if let startDate = match.startDate, let tournament = match.currentTournament() {
if startDate < tournament.startDate {
tournament.startDate = startDate
}
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
do {
try tournamentStore.matches.addOrUpdate(instance: match)
} catch {

@ -104,10 +104,12 @@ struct EventListView: View {
TournamentCellView(tournament: tournament)
}
.contextMenu {
Button {
navigation.openTournamentInOrganizer(tournament)
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
if tournament.hasEnded() == false {
Button {
navigation.openTournamentInOrganizer(tournament)
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
}
}
}
#if DEBUG

@ -15,8 +15,8 @@ struct OngoingView: View {
@State private var sortByField: Bool = false
let fieldSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.courtIndex!), .keyPath(\Match.startDate!)]
let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.startDate!), .keyPath(\Match.courtIndex!)]
let fieldSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.courtIndexForSorting), .keyPath(\Match.startDate!)]
let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.startDate!), .keyPath(\Match.courtIndexForSorting)]
var matches: [Match] {
let sorting = self.sortByField ? fieldSorting : defaultSorting
@ -65,35 +65,17 @@ struct OngoingView: View {
}
}
.navigationTitle("En cours")
.navigationBarTitleDisplayMode(.inline)
.toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Menu {
Picker(selection: $sortByField) {
Text("Trier par date").tag(false)
Text("Trier par terrain").tag(true)
} label: {
}
#if DEBUG
Button("effacer les mauvais matchs (TODO)") {
// let bad = matches.filter({ $0.currentTournament() == nil })
// do {
// try self.tournamentStore.matches.delete(contentOfs: bad)
// } catch {
// Logger.error(error)
// }
}
#endif
//todo
//presentFilterView.toggle()
ToolbarItem(placement: .principal) {
Picker(selection: $sortByField) {
Text("tri par date").tag(true)
Text("tri par terrain").tag(false)
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.resizable()
.scaledToFit()
.frame(minHeight: 28)
}
//.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none)
.pickerStyle(.segmented)
}
ToolbarItem(placement: .status) {
if matches.isEmpty == false {

@ -32,15 +32,28 @@ struct TournamentOrganizerView: View {
}
Divider()
HStack {
ScrollView(.horizontal) {
HStack {
let tournaments = dataStore.tournaments.filter({ $0.hasEnded() == false && $0.isDeleted == false && $0.isCanceled == false }).sorted(by: \.startDate).reversed()
ForEach(tournaments) { tournament in
TournamentButtonView(tournament: tournament)
ScrollViewReader { proxy in
ScrollView(.horizontal) {
HStack {
let tournaments = dataStore.tournaments.filter({ $0.hasEnded() == false && $0.isDeleted == false && $0.isCanceled == false }).sorted(by: \.startDate).reversed()
ForEach(tournaments) { tournament in
TournamentButtonView(tournament: tournament)
.id(tournament.id)
}
}
.padding()
.buttonStyle(.plain)
}
.onAppear {
if let selectedDestination = navigation.organizerTournament {
proxy.scrollTo(selectedDestination.id, anchor: .center)
}
}
.onChange(of: navigation.organizerTournament) {
if let selectedDestination = navigation.organizerTournament {
proxy.scrollTo(selectedDestination.id, anchor: .center)
}
}
.padding()
.buttonStyle(.plain)
}
}
}

@ -33,7 +33,7 @@ struct CourtAvailabilitySettingsView: View {
ForEach(keys, id: \.self) { key in
if let dates = courtsUnavailability[key] {
Section {
ForEach(dates) { dateInterval in
ForEach(dates.sorted(by: \.startDate)) { dateInterval in
Menu {
Button("dupliquer") {
let duplicatedDateInterval = DateInterval(event: event.id, courtIndex: (courtIndex+1)%tournament.courtCount, startDate: dateInterval.startDate, endDate: dateInterval.endDate)
@ -138,7 +138,18 @@ struct CourtAvailabilitySettingsView: View {
Section {
DatePicker("Début", selection: $startDate)
.onChange(of: startDate) {
if endDate < startDate {
endDate = startDate.addingTimeInterval(90*60)
}
}
DatePicker("Fin", selection: $endDate)
.onChange(of: endDate) {
if startDate > endDate {
startDate = endDate.addingTimeInterval(-90*60)
}
}
} footer: {
FooterButtonView("jour entier") {
startDate = startDate.startOfDay

@ -60,7 +60,14 @@ struct EditablePlayerView: View {
}
}
}
.onChange(of: editedLicenceId) {
self.player.licenceId = editedLicenceId
do {
try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player)
} catch {
Logger.error(error)
}
}
}
@ViewBuilder

@ -119,15 +119,9 @@ struct PlayerDetailView: View {
}
} label: {
Text("Licence")
CopyPasteButtonView(pasteValue: player.licenceId?.strippedLicense)
}
RowButtonView("Copier dans le presse-papier") {
let pasteboard = UIPasteboard.general
pasteboard.string = player.licenceId?.strippedLicense
}
}
Section {
LabeledContent {
TextField("Téléphone", text: $phoneNumber)
.keyboardType(.namePhonePad)
@ -140,15 +134,9 @@ struct PlayerDetailView: View {
}
} label: {
Text("Téléphone")
CopyPasteButtonView(pasteValue: player.phoneNumber)
}
RowButtonView("Copier dans le presse-papier") {
let pasteboard = UIPasteboard.general
pasteboard.string = player.phoneNumber
}
}
Section {
LabeledContent {
TextField("Email", text: $email)
.keyboardType(.emailAddress)
@ -161,11 +149,7 @@ struct PlayerDetailView: View {
}
} label: {
Text("Email")
}
RowButtonView("Copier dans le presse-papier") {
let pasteboard = UIPasteboard.general
pasteboard.string = player.email
CopyPasteButtonView(pasteValue: player.email)
}
}
@ -173,7 +157,7 @@ struct PlayerDetailView: View {
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: player.pasteData()) {
Label("Partagez", systemImage: "square.and.arrow.up")
Label("Partager", systemImage: "square.and.arrow.up")
}
}
}

@ -73,13 +73,7 @@ struct RoundSettingsView: View {
Text("Suite à un changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.")
}
}
Section {
ShareLink(item: tournament.rounds().compactMap { $0.pasteData() }.joined(separator: "\n\n")) {
Text("Exportez l'état du tableau")
}
}
// Section {
// RowButtonView("Enabled", role: .destructive) {
// let allMatches = tournament._allMatchesIncludingDisabled()
@ -160,6 +154,11 @@ struct RoundSettingsView: View {
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: tournament.rounds().compactMap { $0.pasteData() }.joined(separator: "\n\n"))
}
}
}
private func _removeAllSeeds() async {

@ -10,14 +10,28 @@ import LeStorage
struct EditingTeamView: View {
@EnvironmentObject var dataStore: DataStore
@EnvironmentObject var networkMonitor: NetworkMonitor
@Environment(Tournament.self) var tournament: Tournament
var team: TeamRegistration
@State private var editedTeam: TeamRegistration?
@State private var contactType: ContactType? = nil
@State private var sentError: ContactManagerError? = nil
@State private var showSubscriptionView: Bool = false
@State private var registrationDate : Date
@State private var callDate : Date
@State private var name: String
@Environment(Tournament.self) var tournament: Tournament
var messageSentFailed: Binding<Bool> {
Binding {
sentError != nil
} set: { newValue in
if newValue == false {
sentError = nil
}
}
}
var tournamentStore: TournamentStore {
return self.tournament.tournamentStore
}
@ -32,52 +46,111 @@ struct EditingTeamView: View {
var body: some View {
List {
Section {
TextField("Nom de l'équipe", text: $name)
.autocorrectionDisabled()
.keyboardType(.alphabet)
.frame(maxWidth: .infinity)
.submitLabel(.done)
.onSubmit(of: .text) {
let trimmed = name.trimmed
if trimmed.isEmpty {
team.name = nil
} else {
team.name = trimmed
}
_save()
}
RowButtonView("Modifier la composition de l'équipe") {
editedTeam = team
}
TeamDetailView(team: team)
} footer: {
HStack {
CopyPasteButtonView(pasteValue: team.playersPasteData())
Spacer()
NavigationLink {
GroupStageTeamReplacementView(team: team)
.environment(tournament)
} label: {
Text("Chercher à remplacer")
}
}
}
Section {
DatePicker(registrationDate.localizedWeekDay(), selection: $registrationDate)
} header: {
Text("Date d'inscription")
}
Section {
DatePicker(selection: $registrationDate) {
Text("Date d'inscription")
Text(registrationDate.localizedWeekDay())
}
if let callDate = team.callDate {
LabeledContent() {
Text(callDate.localizedDate())
} label: {
Text("OK")
Text("Convocation")
}
} else {
Text("Cette équipe n'a pas été convoquée")
}
} header: {
Text("Statut de la convocation")
}
Section {
Toggle(isOn: hasArrived) {
Text("Équipe sur place")
}
/*
Toggle(isOn: $team.confirmedCall) {
Text("Équipe sur place")
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
})) {
Text("Wildcard Tableau")
}
Toggle(isOn: .init(get: {
return team.wildCardGroupStage
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.walkOut = false
team.wildCardGroupStage = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
})) {
Text("Wildcard Poule")
}
Toggle(isOn: .init(get: {
return team.walkOut
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
})) {
Text("Forfait")
}
*/
}
Section {
TextField("Nom de l'équipe", text: $name)
.autocorrectionDisabled()
.keyboardType(.alphabet)
.frame(maxWidth: .infinity)
.submitLabel(.done)
.onSubmit(of: .text) {
let trimmed = name.trimmed
if trimmed.isEmpty {
team.name = nil
} else {
team.name = trimmed
}
_save()
}
}
Section {
@ -95,15 +168,101 @@ struct EditingTeamView: View {
}
.disabled(team.inRound() == false)
}
Section {
RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") {
team.deleteTeamScores()
do {
try tournamentStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
}
}
}
}
.alert("Un problème est survenu", isPresented: messageSentFailed) {
Button("OK") {
}
} message: {
Text(_getErrorMessage())
}
.sheet(item: $contactType) { contactType in
Group {
switch contactType {
case .message(_, let recipients, let body, _):
if Guard.main.paymentForNewTournament() != nil {
MessageComposeView(recipients: recipients, body: body) { result in
switch result {
case .cancelled:
break
case .failed:
self.sentError = .messageFailed
case .sent:
if networkMonitor.connected == false {
self.sentError = .messageNotSent
}
@unknown default:
break
}
}
} else {
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
.environment(\.colorScheme, .light)
}
case .mail(_, let recipients, let bccRecipients, let body, let subject, _):
if Guard.main.paymentForNewTournament() != nil {
MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in
switch result {
case .cancelled, .saved:
self.contactType = nil
case .failed:
self.contactType = nil
self.sentError = .mailFailed
case .sent:
if networkMonitor.connected == false {
self.contactType = nil
self.sentError = .mailNotSent
}
@unknown default:
break
}
}
} else {
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
.environment(\.colorScheme, .light)
}
}
}
.tint(.master)
}
.sheet(item: $editedTeam) { editedTeam in
NavigationStack {
AddTeamView(tournament: tournament, editedTeam: editedTeam)
}
.tint(.master)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
}
}
.onChange(of: registrationDate) {
team.registrationDate = registrationDate
_save()
}
.headerProminence(.increased)
.navigationTitle("Statut de l'équipe")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Édition de l'équipe")
.navigationBarTitleDisplayMode(.inline)
}
private func _getErrorMessage() -> String {
let m1 : String? = (networkMonitor.connected == false ? "L'appareil n'est pas connecté à internet." : nil)
let m2 : String? = (sentError == .mailNotSent ? "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer." : nil)
let m3 : String? = ((sentError == .messageFailed || sentError == .messageNotSent) ? "Le SMS n'a pas été envoyé" : nil)
let m4 : String? = (sentError == .mailFailed ? "Le mail n'a pas été envoyé" : nil)
let message : String = [m1, m2, m3, m4].compacted().joined(separator: "\n")
return message
}
private var hasArrived: Binding<Bool> {

@ -524,7 +524,7 @@ struct InscriptionManagerView: View {
ForEach(teams) { team in
let teamIndex = team.index(in: sortedTeams)
NavigationLink {
_teamCompactTeamEditionView(team)
EditingTeamView(team: team)
.environment(tournament)
} label: {
TeamRowView(team: team)
@ -572,113 +572,6 @@ struct InscriptionManagerView: View {
.autocorrectionDisabled()
}
func _teamCompactTeamEditionView(_ team: TeamRegistration) -> some View {
List {
Section {
TeamDetailView(team: team)
} footer: {
FooterButtonView("Copier dans le presse-papier") {
let pasteboard = UIPasteboard.general
pasteboard.string = team.playersPasteData()
}
}
Section {
NavigationLink {
EditingTeamView(team: team)
.environment(tournament)
} label: {
Text("Modifier le statut de l'équipe")
Text("Nom de l'équipe, date d'inscription, présence, position")
}
NavigationLink {
GroupStageTeamReplacementView(team: team)
.environment(tournament)
} label: {
Text("Chercher à remplacer")
}
RowButtonView("Modifier la composition de l'équipe") {
editedTeam = team
}
}
Section {
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Wildcard Tableau")
}
Toggle(isOn: .init(get: {
return team.wildCardGroupStage
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.walkOut = false
team.wildCardGroupStage = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Wildcard Poule")
}
}
Section {
Toggle(isOn: .init(get: {
return team.walkOut
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Forfait")
}
RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") {
team.deleteTeamScores()
do {
try tournamentStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
}
_setHash()
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
}
}
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Édition de l'équipe")
.navigationBarTitleDisplayMode(.inline)
}
@ViewBuilder
func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View {
Section {
@ -1044,96 +937,12 @@ struct InscriptionManagerView: View {
Text(formattedRegistrationDate)
}
Spacer()
Menu {
_teamMenuOptionView(team)
} label: {
LabelOptions().labelStyle(.titleOnly)
}
}
}
@ViewBuilder
private func _teamMenuOptionView(_ team: TeamRegistration) -> some View {
Section {
NavigationLink {
GroupStageTeamReplacementView(team: team)
} label: {
Text("Chercher à remplacer")
}
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
//Divider()
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = team.playersPasteData()
}
//Divider()
Button("Changer les joueurs") {
editedTeam = team
}
Divider()
NavigationLink {
EditingTeamView(team: team)
.environment(tournament)
} label: {
Text("Modifier une donnée de l'équipe")
}
Divider()
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle")
}
Toggle(isOn: .init(get: {
return team.wildCardGroupStage
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.walkOut = false
team.wildCardGroupStage = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle")
}
Divider()
Toggle(isOn: .init(get: {
return team.walkOut
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle")
LabelOptions().labelStyle(.titleOnly)
}
Divider()
_teamDeleteButtonView(team)
// } header: {
// Text(team.teamLabel(.short))
}
}

@ -253,9 +253,15 @@ struct TableStructureView: View {
private func _saveWithoutRebuild() {
tournament.teamCount = teamCount
tournament.groupStageCount = groupStageCount
let updateGroupStageState = teamsPerGroupStage > tournament.teamsPerGroupStage
tournament.teamsPerGroupStage = teamsPerGroupStage
tournament.qualifiedPerGroupStage = qualifiedPerGroupStage
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified
if updateGroupStageState {
tournament.groupStages().forEach { groupStage in
groupStage.updateGroupStageState()
}
}
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {

@ -20,9 +20,6 @@ struct TournamentRankView: View {
var tournamentStore: TournamentStore {
return self.tournament.tournamentStore
}
@State private var runningMatches: [Match]?
@State private var matchesLeft: [Match]?
var isEditingTeam: Binding<Bool> {
Binding {
@ -37,6 +34,10 @@ struct TournamentRankView: View {
let rankingsCalculated = tournament.selectedSortedTeams().anySatisfy({ $0.finalRanking != nil })
if editMode?.wrappedValue.isEditing == false {
Section {
let all = tournament.allMatches()
let runningMatches = tournament.runningMatches(all)
let matchesLeft = tournament.readyMatches(all)
MatchListView(section: "Matchs restant", matches: matchesLeft, hideWhenEmpty: false, isExpanded: false)
MatchListView(section: "Matchs en cours", matches: runningMatches, hideWhenEmpty: false, isExpanded: false)
@ -105,11 +106,6 @@ struct TournamentRankView: View {
}
}
}
.task {
let all = tournament.allMatches()
self.runningMatches = await tournament.asyncRunningMatches(all)
self.matchesLeft = await tournament.readyMatches(all)
}
.alert("Position", isPresented: isEditingTeam) {
if let selectedTeam {
@Bindable var team = selectedTeam

Loading…
Cancel
Save