match in progress

multistore
Razmig Sarkissian 2 years ago
parent 38d2f7d005
commit 230181fe31
  1. 68
      PadelClub.xcodeproj/project.pbxproj
  2. 12
      PadelClub/Data/DataStore.swift
  3. 108
      PadelClub/Data/GroupStage.swift
  4. 107
      PadelClub/Data/Match.swift
  5. 6
      PadelClub/Data/MockData.swift
  6. 46
      PadelClub/Data/PlayerRegistration.swift
  7. 53
      PadelClub/Data/Round.swift
  8. 68
      PadelClub/Data/TeamRegistration.swift
  9. 52
      PadelClub/Data/TeamScore.swift
  10. 158
      PadelClub/Data/Tournament.swift
  11. 8
      PadelClub/Manager/DisplayContext.swift
  12. 26
      PadelClub/Views/Components/Labels.swift
  13. 376
      PadelClub/Views/GroupStage/GroupStageView.swift
  14. 304
      PadelClub/Views/GroupStage/GroupStagesView.swift
  15. 23
      PadelClub/Views/Match/MatchDetailView.swift
  16. 35
      PadelClub/Views/Match/MatchRowView.swift
  17. 24
      PadelClub/Views/Match/MatchSummaryView.swift
  18. 3
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  19. 21
      PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift
  20. 19
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  21. 8
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  22. 4
      PadelClub/Views/Tournament/TournamentInitView.swift
  23. 30
      PadelClub/Views/Tournament/TournamentRunningView.swift
  24. 31
      PadelClub/Views/Tournament/TournamentView.swift

@ -81,6 +81,19 @@
FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; };
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; };
FF8F26542BAE1E4400650388 /* TableStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26532BAE1E4400650388 /* TableStructureView.swift */; };
FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CE72BAEC70100A9A3BD /* GroupStage.swift */; };
FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEB2BAECB9900A9A3BD /* Match.swift */; };
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CED2BAECBD700A9A3BD /* Round.swift */; };
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */; };
FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */; };
FF967CF42BAECC0B00A9A3BD /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */; };
FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */; };
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF72BAEDF0000A9A3BD /* Labels.swift */; };
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */; };
FF967CFD2BAEE5F500A9A3BD /* GroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */; };
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; };
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */; };
FF967D042BAEF1C300A9A3BD /* MatchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */; };
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
@ -220,6 +233,19 @@
FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTypeSelectionView.swift; sourceTree = "<group>"; };
FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatPickerView.swift; sourceTree = "<group>"; };
FF8F26532BAE1E4400650388 /* TableStructureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableStructureView.swift; sourceTree = "<group>"; };
FF967CE72BAEC70100A9A3BD /* GroupStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStage.swift; sourceTree = "<group>"; };
FF967CEB2BAECB9900A9A3BD /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = "<group>"; };
FF967CED2BAECBD700A9A3BD /* Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Round.swift; sourceTree = "<group>"; };
FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamScore.swift; sourceTree = "<group>"; };
FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRegistration.swift; sourceTree = "<group>"; };
FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRegistration.swift; sourceTree = "<group>"; };
FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentRunningView.swift; sourceTree = "<group>"; };
FF967CF72BAEDF0000A9A3BD /* Labels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labels.swift; sourceTree = "<group>"; };
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageView.swift; sourceTree = "<group>"; };
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStagesView.swift; sourceTree = "<group>"; };
FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchRowView.swift; sourceTree = "<group>"; };
FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchSummaryView.swift; sourceTree = "<group>"; };
FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDetailView.swift; sourceTree = "<group>"; };
FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; };
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
@ -349,6 +375,12 @@
C4A47D752B73787D00ADC637 /* Migration */,
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */,
C4A47D592B6D383C00ADC637 /* Tournament.swift */,
FF967CE72BAEC70100A9A3BD /* GroupStage.swift */,
FF967CED2BAECBD700A9A3BD /* Round.swift */,
FF967CEB2BAECB9900A9A3BD /* Match.swift */,
FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */,
FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */,
FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */,
C4A47D622B6D3D6500ADC637 /* Club.swift */,
FF8F263E2BAD7D5C00650388 /* Event.swift */,
FF1DC5522BAB354A00FD8220 /* MockData.swift */,
@ -366,6 +398,8 @@
FF39719B2B8DE04B004C4E75 /* Navigation */,
FF8F26392BAD526A00650388 /* Event */,
FF1DC54D2BAB34FA00FD8220 /* Club */,
FF967CF92BAEE11500A9A3BD /* GroupStage */,
FF967CFE2BAEEF5A00A9A3BD /* Match */,
FF3F74F72B919F96004CFE0E /* Tournament */,
C4A47D882B7BBB5000ADC637 /* Subscription */,
C4A47D852B7BA33F00ADC637 /* User */,
@ -415,6 +449,7 @@
children = (
C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */,
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */,
FF967CF72BAEDF0000A9A3BD /* Labels.swift */,
);
path = Components;
sourceTree = "<group>";
@ -449,6 +484,7 @@
children = (
FF70916B2B91005400AB08DA /* TournamentView.swift */,
FF8F26402BADFC8700650388 /* TournamentInitView.swift */,
FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */,
FF3F74F92B91A018004CFE0E /* Screen */,
FF3F74F82B919FB2004CFE0E /* Shared */,
);
@ -572,6 +608,25 @@
path = Components;
sourceTree = "<group>";
};
FF967CF92BAEE11500A9A3BD /* GroupStage */ = {
isa = PBXGroup;
children = (
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */,
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */,
);
path = GroupStage;
sourceTree = "<group>";
};
FF967CFE2BAEEF5A00A9A3BD /* Match */ = {
isa = PBXGroup;
children = (
FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */,
FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */,
FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */,
);
path = Match;
sourceTree = "<group>";
};
FFD783FB2B91B919000F62A6 /* Agenda */ = {
isa = PBXGroup;
children = (
@ -786,6 +841,7 @@
FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */,
FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */,
C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */,
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */,
FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */,
C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */,
@ -796,15 +852,20 @@
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */,
FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */,
FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */,
FF967D042BAEF1C300A9A3BD /* MatchRowView.swift in Sources */,
FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */,
C4A47D742B72881F00ADC637 /* ClubView.swift in Sources */,
C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */,
FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */,
FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */,
FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */,
FF6EC9062B947A1000EA7F5A /* NetworkManagerError.swift in Sources */,
C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */,
C4A47D7B2B73C0F900ADC637 /* TournamentV2.swift in Sources */,
FF3795662B9399AA004EA093 /* Persistence.swift in Sources */,
FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */,
FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */,
C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */,
FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */,
@ -814,6 +875,7 @@
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */,
FF8F26542BAE1E4400650388 /* TableStructureView.swift in Sources */,
FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */,
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */,
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
@ -830,6 +892,7 @@
FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */,
FF967CFC2BAEE52E00A9A3BD /* GroupStagesView.swift in Sources */,
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */,
FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */,
C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */,
@ -840,18 +903,23 @@
C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */,
C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */,
FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */,
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */,
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */,
FF8F26382BAD523300650388 /* PadelRule.swift in Sources */,
FF967CF42BAECC0B00A9A3BD /* TeamRegistration.swift in Sources */,
FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */,
FF967CFD2BAEE5F500A9A3BD /* GroupStageView.swift in Sources */,
FF1DC5592BAB767000FD8220 /* Tips.swift in Sources */,
FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */,
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */,
FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */,
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,
C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */,
FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */,
C4A47D912B7BBBEC00ADC637 /* Guard.swift in Sources */,

@ -18,6 +18,12 @@ class DataStore: ObservableObject {
fileprivate(set) var tournaments: StoredCollection<Tournament>
fileprivate(set) var clubs: StoredCollection<Club>
fileprivate(set) var events: StoredCollection<Event>
fileprivate(set) var groupStages: StoredCollection<GroupStage>
fileprivate(set) var matches: StoredCollection<Match>
fileprivate(set) var teamRegistrations: StoredCollection<TeamRegistration>
fileprivate(set) var playerRegistrations: StoredCollection<PlayerRegistration>
fileprivate(set) var rounds: StoredCollection<Round>
fileprivate(set) var teamScores: StoredCollection<TeamScore>
fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json")
@ -40,6 +46,12 @@ class DataStore: ObservableObject {
self.clubs = store.registerCollection(synchronized: false)
self.tournaments = store.registerCollection(synchronized: false)
self.events = store.registerCollection(synchronized: false)
self.groupStages = store.registerCollection(synchronized: false)
self.teamScores = store.registerCollection(synchronized: false)
self.teamRegistrations = store.registerCollection(synchronized: false)
self.playerRegistrations = store.registerCollection(synchronized: false)
self.rounds = store.registerCollection(synchronized: false)
self.matches = store.registerCollection(synchronized: false)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil)

@ -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"
}
}

@ -26,3 +26,9 @@ extension Tournament {
Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior)
}
}
extension Match {
static func mock() -> Match {
Match(index: 0, broadcasted: false, order: 0)
}
}

@ -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"
}
}

@ -78,7 +78,27 @@ class Tournament : ModelObject, Storable {
self.teamsPerGroupStage = teamsPerGroupStage
self.entryFee = entryFee
}
enum State {
case initial
case build
}
func state() -> Tournament.State {
if groupStageCount > 0 && groupStages.isEmpty == false {
return .build
}
return .initial
}
var groupStages: [GroupStage] {
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index)
}
var teamRegistrations: [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }
}
var rounds: Int {
4
}
@ -87,6 +107,10 @@ class Tournament : ModelObject, Storable {
[tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ")
}
func subtitle(_ displayStyle: DisplayStyle = .wide) -> String {
name ?? ""
}
func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide:
@ -96,6 +120,53 @@ class Tournament : ModelObject, Storable {
}
}
func qualifiedFromGroupStage() -> Int {
groupStageCount * qualifiedPerGroupStage
}
func qualifiedTeams() -> [TeamRegistration] {
teamRegistrations.filter({ $0.qualified() })
}
func moreQualifiedToDraw() -> Int {
max(qualifiedTeams().count - (qualifiedFromGroupStage() + groupStageAdditionalQualified), 0)
}
func missingQualifiedFromGroupStages() -> [TeamRegistration] {
if groupStageAdditionalQualified > 0 {
return groupStages.filter { $0.hasEnded() }.compactMap { groupStage in
groupStage.teams[qualifiedPerGroupStage]
}
.filter({ $0.qualified() == false })
} else {
return []
}
}
func groupStagesAreOver() -> Bool {
guard groupStages.isEmpty == false else {
return true
}
return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified
}
func groupStageStatus() -> String {
let runningGroupStages = groupStages.filter({ $0.isRunning() })
if groupStagesAreOver() { return "terminées" }
if runningGroupStages.isEmpty {
let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false })
if ongoingGroupStages.isEmpty == false {
return "Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { $0.index.formatted() }.joined(separator: ", ") + " en cours"
}
return groupStages.count.formatted() + " poule" + groupStages.count.pluralSuffix
} else {
return "Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { $0.index.formatted() }.joined(separator: ", ") + " en cours"
}
}
func settingsDescriptionLocalizedLabel() -> String {
[dayDuration.formatted() + " jour\(dayDuration.pluralSuffix)", courtCount.formatted() + " terrain\(courtCount.pluralSuffix)"].joined(separator: ", ")
}
@ -105,15 +176,84 @@ class Tournament : ModelObject, Storable {
return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
}
}
func buildStructure() {
buildGroupStages()
}
func buildGroupStages() {
groupStages.forEach { groupStage in
try? DataStore.shared.groupStages.delete(instance: groupStage)
}
var _groupStages = [GroupStage]()
for index in 0..<groupStageCount {
let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, matchFormat: groupStageMatchFormat)
_groupStages.append(groupStage)
}
try? DataStore.shared.groupStages.append(contentOfs: _groupStages)
groupStages.forEach { $0.buildMatches() }
refreshBrackets()
}
func resetStructure() {
}
func resetGroupStages() {
}
func refreshBrackets() {
// completeEntries.forEach { entrant in
// if entrant.bracketPosition > 0 {
// entrant.resetBracketPosition()
// }
// }
//
if groupStageCount > 0 {
switch groupStageOrderingMode {
case .random:
setBrackets(randomize: true)
case .snake:
setBrackets(randomize: false)
case .swiss:
setBrackets(randomize: true)
}
}
}
func setBrackets(randomize: Bool) {
let numberOfBracketsAsInt = groupStages.count
// let teamsPerBracket = Int(teamsPerBracket)
if groupStageCount != numberOfBracketsAsInt {
buildGroupStages()
return
}
let max = groupStages.map { $0.size }.reduce(0,+)
// var chunks = orderedEntries.filter { $0.wcFinalTable == false }.suffix(Int(max)).chunked(into: numberOfBracketsAsInt)
// for (index, _) in chunks.enumerated() {
// if randomize {
// chunks[index].shuffle()
// } else if index % 2 != 0 {
// chunks[index].reverse()
// }
//
// print("Equipes \(chunks[index].map { $0.initialRank })")
// for (jIndex, _) in chunks[index].enumerated() {
// print("Position \(index+1) Poule \(orderedBrackets[jIndex].index)")
// chunks[index][jIndex].bracketPosition = orderedBrackets[jIndex].index
// chunks[index][jIndex].bracketPositions = [Int(index + 1)]
// }
// }
}
extension Tournament {
func isFree() -> Bool {
entryFee == nil || entryFee == 0
}
}
extension Tournament {
var teamSortingType: TeamSortingType {
get {
TeamSortingType(rawValue: teamSorting) ?? tournamentLevel.defaultTeamSortingType
@ -255,16 +395,6 @@ extension Tournament {
}
}
extension Tournament {
func state() -> Tournament.State {
.initial
}
enum State {
case initial
}
}
extension Tournament: Hashable {
static func == (lhs: Tournament, rhs: Tournament) -> Bool {
lhs.id == rhs.id

@ -16,3 +16,11 @@ enum DisplayStyle {
case wide
case short
}
enum MatchViewStyle {
case standardStyle // vue normal
case sectionedStandardStyle // vue normal avec des sections indiquant déjà la manche
case feedStyle // vue programmation
case plainStyle // vue detail
case tournamentResultStyle //vue resultat tournoi
}

@ -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())
}

@ -129,7 +129,8 @@ struct ActivityView: View {
}
.navigationTitle(TabDestination.activity.title)
.navigationDestination(for: Tournament.self) { tournament in
TournamentView(tournament: tournament)
TournamentView()
.environment(tournament)
}
}
}

@ -8,20 +8,15 @@
import SwiftUI
struct OrganizedTournamentView: View {
let tournament: Tournament
@State private var navigationPath: [Screen] = []
init(tournament: Tournament) {
self.tournament = tournament
_navigationPath = State(wrappedValue: tournament.navigationPath)
}
let tournament: Tournament
var body: some View {
NavigationStack(path: $navigationPath) {
TournamentView(tournament: tournament, presentationContext: .organizer)
.onChange(of: navigationPath) {
tournament.navigationPath = navigationPath
}
@Bindable var tournament = tournament
NavigationStack(path: $tournament.navigationPath) {
TournamentView(presentationContext: .organizer)
.environment(tournament)
// .onChange(of: navigationPath) {
// tournament.navigationPath = navigationPath
// }
}
}
}

@ -193,18 +193,15 @@ struct TableStructureView: View {
if tournament.state() == .initial {
Button("Valider") {
_save(rebuildEverything: true)
dismiss()
}
.clipShape(Capsule())
.buttonStyle(.bordered)
.disabled(updatedElements.isEmpty)
} else {
let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding })
Button("Valider", role: .destructive) {
if requirements.isEmpty {
_save(rebuildEverything: false)
dismiss()
} else {
presentRefreshStructureWarning = true
}
@ -217,13 +214,11 @@ struct TableStructureView: View {
if requirements.allSatisfy({ $0 == .groupStage }) {
Button("Mettre à jour les poules") {
_save(rebuildEverything: false)
dismiss()
}
}
Button("Tout mettre à jour", role: .destructive) {
_save(rebuildEverything: true)
dismiss()
}
}, message: {
@ -243,14 +238,7 @@ struct TableStructureView: View {
let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding })
if (rebuildEverything == false && requirements.contains(.all)) || rebuildEverything {
// if let matches = tournament.matchs {
// tournament.removeFromMatchs(matches)
// }
// tournament.additionalRounds = 0
// tournament.orderedEntries.forEach { entrant in
// entrant.initialPosition = 0
// }
// tournament.hiddenRounds = nil
tournament.resetStructure()
}
tournament.teamCount = teamCount
@ -260,13 +248,14 @@ struct TableStructureView: View {
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified
if (rebuildEverything == false && requirements.contains(.all)) || rebuildEverything {
// tournament.build()
tournament.buildStructure()
} else if (rebuildEverything == false && requirements.contains(.groupStage)) {
// tournament.buildGroupStages()
tournament.buildGroupStages()
}
try dataStore.tournaments.addOrUpdate(instance: tournament)
dismiss()
} 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.

@ -19,11 +19,13 @@ struct TournamentCellView: View {
.fill(color)
.frame(width: 2)
VStack(alignment: .leading, spacing: -2) {
Text("Homme")
Text(tournament.subtitle())
.font(.caption2)
Text(tournament.title())
Text(tournament.tournamentLevel.localizedLabel())
.font(.title)
Text("Senior")
Text(tournament.tournamentCategory.localizedLabel())
.font(.caption2)
Text(tournament.federalTournamentAge.localizedLabel())
.font(.caption2)
}
}

@ -17,7 +17,7 @@ struct TournamentInitView: View {
LabeledContent {
Text(tournament.settingsDescriptionLocalizedLabel())
} label: {
Label("Réglages", systemImage: "slider.horizontal.3")
LabelSettings()
}
}
} footer: {
@ -29,7 +29,7 @@ struct TournamentInitView: View {
LabeledContent {
Text(tournament.structureDescriptionLocalizedLabel())
} label: {
Label("Structure", systemImage: "hammer")
LabelStructure()
}
}
} footer: {

@ -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())
}

@ -8,7 +8,7 @@
import SwiftUI
struct TournamentView: View {
@State var tournament: Tournament
@Environment(Tournament.self) var tournament: Tournament
var presentationContext: PresentationContext = .agenda
var body: some View {
@ -16,6 +16,8 @@ struct TournamentView: View {
switch tournament.state() {
case .initial:
TournamentInitView()
case .build:
TournamentRunningView()
}
// InscriptionManagerRowView(tournament: tournament)
// NavigationLink(value: Screen.groupStage) {
@ -34,12 +36,11 @@ struct TournamentView: View {
case .inscription:
InscriptionManagerView(tournament: tournament)
case .groupStage:
Text("Poules \(screen.rawValue)")
GroupStagesView()
}
}
.environment(tournament)
})
.environment(tournament)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
@ -50,21 +51,30 @@ struct TournamentView: View {
}
}
if presentationContext == .agenda {
ToolbarItem(placement: .topBarTrailing) {
Menu {
ToolbarItem(placement: .topBarTrailing) {
Menu {
if presentationContext == .agenda {
Button {
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
}
} label: {
Label("Options", systemImage: "ellipsis.circle")
}
Divider()
if tournament.state() == .build {
NavigationLink(value: Screen.settings) {
LabelSettings()
}
NavigationLink(value: Screen.structure) {
LabelStructure()
}
}
} label: {
LabelOptions()
}
}
}
}
struct InscriptionManagerRowView: View {
@ -80,6 +90,7 @@ struct TournamentView: View {
#Preview {
NavigationStack {
TournamentView(tournament: .mock(), presentationContext: .agenda)
TournamentView(presentationContext: .agenda)
.environment(Tournament.mock())
}
}

Loading…
Cancel
Save