multistore
Razmig Sarkissian 2 years ago
parent 9e2882391b
commit 9758ecfcbc
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 19
      PadelClub/Data/Club.swift
  3. 64
      PadelClub/Data/Court.swift
  4. 2
      PadelClub/Data/DataStore.swift
  5. 26
      PadelClub/Data/Event.swift
  6. 64
      PadelClub/Data/Match.swift
  7. 8
      PadelClub/Data/MockData.swift
  8. 14
      PadelClub/Data/Round.swift
  9. 19
      PadelClub/Data/Tournament.swift
  10. 33
      PadelClub/ViewModel/MatchScheduler.swift
  11. 85
      PadelClub/Views/Calling/CallView.swift
  12. 57
      PadelClub/Views/Club/CourtView.swift
  13. 36
      PadelClub/Views/Components/BarButtonView.swift
  14. 20
      PadelClub/Views/Components/FooterButtonView.swift
  15. 3
      PadelClub/Views/Event/EventCreationView.swift
  16. 27
      PadelClub/Views/Match/MatchDetailView.swift
  17. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  18. 24
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  19. 21
      PadelClub/Views/Planning/PlanningSettingsView.swift
  20. 4
      PadelClub/Views/Planning/PlanningView.swift
  21. 52
      PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift
  22. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentDurationManagerView.swift
  23. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift
  24. 73
      PadelClub/Views/Tournament/TournamentView.swift

@ -118,6 +118,7 @@
FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */; };
FF1DC5592BAB767000FD8220 /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5582BAB767000FD8220 /* Tips.swift */; };
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; };
FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; };
FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; };
FF2BE4882B85E27400592328 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; };
@ -220,6 +221,8 @@
FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; };
FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; };
FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; };
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; };
FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; };
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FFCFBFFD2BBBE86600B82851 /* Algorithms */; };
FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; };
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; };
@ -412,6 +415,7 @@
FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubsView.swift; sourceTree = "<group>"; };
FF1DC5582BAB767000FD8220 /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; };
FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayContext.swift; sourceTree = "<group>"; };
FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonView.swift; sourceTree = "<group>"; };
FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = "<group>"; };
@ -513,6 +517,8 @@
FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; };
FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; };
FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = "<group>"; };
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>"; };
FFCFC0012BBC39A600B82851 /* EditScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScoreView.swift; sourceTree = "<group>"; };
FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointSelectionView.swift; sourceTree = "<group>"; };
FFCFC0112BBC3E1A00B82851 /* PointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointView.swift; sourceTree = "<group>"; };
@ -671,6 +677,7 @@
FF025AE82BD1307E00A86CF8 /* MonthData.swift */,
FF1DC5522BAB354A00FD8220 /* MockData.swift */,
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */,
FFC91B002BD85C2F00B29808 /* Court.swift */,
FF6EC9012B94799200EA7F5A /* Coredata */,
FF6EC9022B9479B900EA7F5A /* Federal */,
);
@ -748,6 +755,7 @@
FFBF065D2BBD8040009D6715 /* MatchListView.swift */,
FF967CF72BAEDF0000A9A3BD /* Labels.swift */,
FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */,
FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -869,6 +877,7 @@
FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */,
FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */,
FF5D0D882BB4935C005CB568 /* ClubRowView.swift */,
FFC91B022BD85E2400B29808 /* CourtView.swift */,
);
path = Club;
sourceTree = "<group>";
@ -1443,6 +1452,7 @@
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */,
FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */,
FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */,
FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */,
FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */,
FF92680B2BCEE3E10080F940 /* ContactManager.swift in Sources */,
@ -1467,6 +1477,7 @@
C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */,
C4A47D7B2B73C0F900ADC637 /* TournamentV2.swift in Sources */,
FF3795662B9399AA004EA093 /* Persistence.swift in Sources */,
FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */,
FFF964502BC25E3700EEF017 /* PlanningView.swift in Sources */,
FF967CEC2BAECB9900A9A3BD /* Match.swift in Sources */,
FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */,
@ -1573,6 +1584,7 @@
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */,
FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,
FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */,
FF9267FF2BCE94830080F940 /* CallSettingsView.swift in Sources */,

@ -32,8 +32,7 @@ class Club : ModelObject, Storable, Hashable {
var zipCode: String?
var latitude: Double?
var longitude: Double?
var courtCount: Int?
var courtNames: [String]? = nil
//var courtCount: Int?
internal init(name: String, acronym: String? = nil, phone: String? = nil, code: String? = nil, address: String? = nil, city: String? = nil, zipCode: String? = nil, latitude: Double? = nil, longitude: Double? = nil) {
self.name = name
@ -47,7 +46,21 @@ class Club : ModelObject, Storable, Hashable {
self.longitude = longitude
}
func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide:
return name
case .short:
return acronym
}
}
var courts: [Court] {
Store.main.filter { $0.club == self.id }.sorted(by: \.index)
}
override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.courts)
}
enum CodingKeys: String, CodingKey {
@ -61,8 +74,6 @@ class Club : ModelObject, Storable, Hashable {
case _zipCode = "zipCode"
case _latitude = "latitude"
case _longitude = "longitude"
case _courtCount = "courtCount"
case _courtNames = "courtNames"
}
}

@ -0,0 +1,64 @@
//
// Court.swift
// PadelClub
//
// Created by Razmig Sarkissian on 23/04/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
class Court : ModelObject, Storable, Hashable {
static func resourceName() -> String { return "courts" }
static func == (lhs: Court, rhs: Court) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
return hasher.combine(id)
}
var id: String = Store.randomId()
var index: Int
var club: String
var name: String?
var exitAllowed: Bool = false
var indoor: Bool = false
internal init(club: String, name: String? = nil, index: Int) {
self.club = club
self.name = name
self.index = index
}
func courtTitle() -> String {
self.name ?? courtIndexTitle()
}
func courtIndexTitle() -> String {
Self.courtIndexedTitle(atIndex: index)
}
static func courtIndexedTitle(atIndex index: Int) -> String {
("Terrain #" + (index + 1).formatted())
}
func clubObject() -> Club? {
Store.main.findById(club)
}
override func deleteDependencies() throws {
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _index = "index"
case _club = "club"
case _name = "name"
case _exitAllowed = "exitAllowed"
case _indoor = "indoor"
}
}

@ -17,6 +17,7 @@ class DataStore: ObservableObject {
fileprivate(set) var tournaments: StoredCollection<Tournament>
fileprivate(set) var clubs: StoredCollection<Club>
fileprivate(set) var courts: StoredCollection<Court>
fileprivate(set) var events: StoredCollection<Event>
fileprivate(set) var groupStages: StoredCollection<GroupStage>
fileprivate(set) var matches: StoredCollection<Match>
@ -67,6 +68,7 @@ class DataStore: ObservableObject {
let indexed : Bool = true
self.clubs = store.registerCollection(synchronized: false, indexed: indexed)
self.courts = store.registerCollection(synchronized: false, indexed: indexed)
self.tournaments = store.registerCollection(synchronized: false, indexed: indexed)
self.events = store.registerCollection(synchronized: false, indexed: indexed)
self.groupStages = store.registerCollection(synchronized: false, indexed: indexed)

@ -17,21 +17,21 @@ class Event: ModelObject, Storable {
var club: String?
var creationDate: Date = Date()
var name: String?
var courtCount: Int?
//var courtCount: Int?
var tenupId: String?
var groupStageFormat: Int?
var roundFormat: Int?
var loserRoundFormat: Int?
// var groupStageFormat: Int?
// var roundFormat: Int?
// var loserRoundFormat: Int?
//var timeslots ?
internal init(club: String? = nil, name: String? = nil, courtCount: Int? = nil, tenupId: String? = nil, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil) {
internal init(club: String? = nil, name: String? = nil, tenupId: String? = nil) {
self.club = club
self.name = name
self.courtCount = courtCount
// self.courtCount = courtCount
self.tenupId = tenupId
self.groupStageFormat = groupStageFormat
self.roundFormat = roundFormat
self.loserRoundFormat = loserRoundFormat
// self.groupStageFormat = groupStageFormat
// self.roundFormat = roundFormat
// self.loserRoundFormat = loserRoundFormat
}
var clubObject: Club? {
@ -58,10 +58,10 @@ extension Event {
case _club = "club"
case _creationDate = "creationDate"
case _name = "name"
case _courtCount = "courtCount"
//case _courtCount = "courtCount"
case _tenupId = "tenupId"
case _groupStageFormat = "groupStageFormat"
case _roundFormat = "roundFormat"
case _loserRoundFormat = "loserRoundFormat"
// case _groupStageFormat = "groupStageFormat"
// case _roundFormat = "roundFormat"
// case _loserRoundFormat = "loserRoundFormat"
}
}

@ -20,30 +20,30 @@ class Match: ModelObject, Storable {
var endDate: Date?
var index: Int
var format: Int?
var court: String?
//var court: String?
var servingTeamId: String?
var winningTeamId: String?
var losingTeamId: String?
var broadcasted: Bool
var name: String?
var order: Int
//var broadcasted: Bool
//var name: String?
//var order: Int
var disabled: Bool = false
var courtIndex: Int?
private(set) var courtIndex: 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) {
internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil) {
self.round = round
self.groupStage = groupStage
self.startDate = startDate
self.endDate = endDate
self.index = index
self.format = matchFormat?.rawValue
self.court = court
//self.court = court
self.servingTeamId = servingTeamId
self.winningTeamId = winningTeamId
self.losingTeamId = losingTeamId
self.broadcasted = broadcasted
self.name = name
self.order = order
// self.broadcasted = broadcasted
// self.name = name
// self.order = order
}
func indexInRound() -> Int {
@ -111,7 +111,7 @@ class Match: ModelObject, Storable {
losingTeamId = nil
winningTeamId = nil
endDate = nil
court = nil
removeCourt()
servingTeamId = nil
}
@ -373,10 +373,11 @@ class Match: ModelObject, Storable {
switch fieldSetup {
case .random:
let courtName = availableCourts().randomElement()
court = courtName
case .field(let courtName):
court = courtName
if let _courtIndex = availableCourts().randomElement() {
setCourt(_courtIndex)
}
case .field(let _courtIndex):
setCourt(_courtIndex)
}
} else {
@ -385,10 +386,13 @@ class Match: ModelObject, Storable {
}
}
func getCourtIndex() -> Int? {
guard let court else { return nil }
if let courtIndex = Int(court) { return courtIndex - 1 }
return nil
func courtName() -> String? {
guard let courtIndex else { return nil }
if let courtName = currentTournament()?.courtName(atIndex: courtIndex) {
return courtName
} else {
return Court.courtIndexedTitle(atIndex: courtIndex)
}
}
func courtCount() -> Int {
@ -397,7 +401,7 @@ class Match: ModelObject, Storable {
func courtIsAvailable(_ courtIndex: Int) -> Bool {
let courtUsed = currentTournament()?.courtUsed() ?? []
return courtUsed.contains(String(courtIndex)) == false
return courtUsed.contains(courtIndex) == false
// return Set(availableCourts().map { String($0) }).subtracting(Set(courtUsed))
}
@ -405,18 +409,18 @@ class Match: ModelObject, Storable {
false
}
func availableCourts() -> [String] {
func availableCourts() -> [Int] {
let courtUsed = currentTournament()?.courtUsed() ?? []
let availableCourts = Array(1...courtCount())
return Array(Set(availableCourts.map { String($0) }).subtracting(Set(courtUsed)))
let availableCourts = Array(0..<courtCount())
return Array(Set(availableCourts.map { $0 }).subtracting(Set(courtUsed)))
}
func removeCourt() {
court = nil
courtIndex = nil
}
func setCourt(_ courtIndex: Int) {
court = String(courtIndex)
self.courtIndex = courtIndex
}
func canBeStarted(inMatches matches: [Match]) -> Bool {
@ -605,14 +609,14 @@ class Match: ModelObject, Storable {
case _endDate = "endDate"
case _index = "index"
case _format = "format"
case _court = "court"
// case _court = "court"
case _courtIndex = "courtIndex"
case _servingTeamId = "servingTeamId"
case _winningTeamId = "winningTeamId"
case _losingTeamId = "losingTeamId"
case _broadcasted = "broadcasted"
case _name = "name"
case _order = "order"
// case _broadcasted = "broadcasted"
// case _name = "name"
// case _order = "order"
case _disabled = "disabled"
}
}
@ -629,7 +633,7 @@ enum MatchDateSetup: Hashable, Identifiable {
enum MatchFieldSetup: Hashable, Identifiable {
case random
// case firstAvailable
case field(String)
case field(Int)
var id: Int { hashValue }
}

@ -7,6 +7,12 @@
import Foundation
extension Court {
static func mock() -> Court {
Court(club: "", name: "Test", index: 0)
}
}
extension Club {
static func mock() -> Club {
Club(name: "AUC", acronym: "AUC")
@ -53,7 +59,7 @@ extension Tournament {
extension Match {
static func mock() -> Match {
Match(index: 0, broadcasted: false, order: 0)
Match(index: 0)
}
}

@ -273,8 +273,8 @@ class Round: ModelObject, Storable {
var cumulativeMatchCount: Int {
var totalMatches = playedMatches().count
if let parent = parentRound {
totalMatches += parent.cumulativeMatchCount
if let parentRound {
totalMatches += parentRound.cumulativeMatchCount
}
return totalMatches
}
@ -294,8 +294,8 @@ class Round: ModelObject, Storable {
var theoryCumulativeMatchCount: Int {
var totalMatches = RoundRule.numberOfMatches(forRoundIndex: index)
if let parent = parentRound {
totalMatches += parent.theoryCumulativeMatchCount
if let parentRound {
totalMatches += parentRound.theoryCumulativeMatchCount
}
return totalMatches
}
@ -329,7 +329,7 @@ class Round: ModelObject, Storable {
if let previousRound = previousRound() {
return previousRound.seedInterval()?.chunks()?.first
} else if let parentRound = parentRound {
} else if let parentRound {
return parentRound.seedInterval()?.chunks()?.last
}
@ -398,8 +398,8 @@ class Round: ModelObject, Storable {
}
var parentRound: Round? {
guard let parentRound = loser else { return nil }
return Store.main.findById(parentRound)
guard let parent = loser else { return nil }
return Store.main.findById(parent)
}
func updateMatchFormat(_ matchFormat: MatchFormat) {

@ -100,15 +100,9 @@ class Tournament : ModelObject, Storable {
case build
}
func getCourtIndex(_ court: String?) -> Int? {
guard let court else { return nil }
if let courtIndex = Int(court) { return courtIndex - 1 }
return nil
}
func courtUsed() -> [String] {
func courtUsed() -> [Int] {
let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id })
return Set(runningMatches.compactMap { $0.court }).sorted()
return Set(runningMatches.compactMap { $0.courtIndex }).sorted()
}
func hasStarted() -> Bool {
@ -1082,6 +1076,15 @@ class Tournament : ModelObject, Storable {
var femaleUnrankedValue: Int? {
currentMonthData()?.femaleUnrankedValue
}
func courtName(atIndex courtIndex: Int) -> String {
let courts = club()?.courts
if let courts, let court = courts.first(where: { $0.index == courtIndex }) {
return court.courtTitle()
} else {
return Court.courtIndexedTitle(atIndex: courtIndex)
}
}
}

@ -260,13 +260,13 @@ class MatchScheduler {
)
}
func getAvailableCourts(from matches: [Match]) -> [(String, Date)] {
let validMatches = matches.filter({ $0.court != nil && $0.startDate != nil })
let byCourt = Dictionary(grouping: validMatches, by: { $0.court! })
func getAvailableCourts(from matches: [Match]) -> [(Int, Date)] {
let validMatches = matches.filter({ $0.courtIndex != nil && $0.startDate != nil })
let byCourt = Dictionary(grouping: validMatches, by: { $0.courtIndex! })
return (byCourt.keys.flatMap { court in
let matchesByCourt = byCourt[court]?.sorted(by: \.startDate!)
let lastMatch = matchesByCourt?.last
var results = [(String, Date)]()
var results = [(Int, Date)]()
if let courtFreeDate = lastMatch?.estimatedEndDate(additionalEstimationDuration) {
results.append((court, courtFreeDate))
}
@ -290,7 +290,7 @@ class MatchScheduler {
rotationIndex += 1
}
let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.getCourtIndex() ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime)
let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.courtIndex ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime)
slots.append(timeMatch)
}
@ -463,27 +463,6 @@ class MatchScheduler {
let upperRounds = tournament.rounds()
let allMatches = tournament.allMatches()
// if dayOne < tournament.courtCount {
// let startOfDay = startDate.startOfDay
// let endOfday = Calendar.current.dateInterval(of: .day, for: startOfDay)!.end
// let dateInterval = DateInterval(startDate: startOfDay, endDate: endOfday)
// let _courtsUnavailability = courtsUnavailability ?? [:]()
// let data = _courtsUnavailability[courtCount - 1] ?? [DateInterval]()
// data.append(dateInterval)
// courtsUnavailability = _courtsUnavailability
// }
//
// if dayTwo < tournament.courtCount {
// let startOfDay = startDate.startOfDay
// let endOfday = Calendar.current.dateInterval(of: .day, for: startOfDay)!.end
// let dateInterval = DateInterval(startDate: startOfDay, endDate: endOfday)
// let _courtsUnavailability = courtsUnavailability ?? [:]()
// let data = _courtsUnavailability[courtCount - 1] ?? [DateInterval]()
// data.append(dateInterval)
// courtsUnavailability = _courtsUnavailability
// }
let rounds = upperRounds.map {
$0
@ -520,7 +499,7 @@ class MatchScheduler {
let usedCourts = getAvailableCourts(from: allMatches.filter({ $0.startDate?.isEarlierThan(startDate) == true && $0.startDate?.dayInt == startDate.dayInt }))
let initialCourts = usedCourts.filter { (court, availableDate) in
availableDate <= startDate
}.sorted(by: \.1).compactMap { tournament.getCourtIndex($0.0) }
}.sorted(by: \.1).compactMap { $0.0 }
let courts : [Int]? = initialCourts.isEmpty ? nil : initialCourts

@ -118,62 +118,57 @@ struct CallView: View {
Text(message)
}
.sheet(item: $contactType) { contactType in
switch contactType {
case .message(_, let recipients, let body, _):
if Guard.main.paymentForNewTournament() != nil {
MessageComposeView(recipients: recipients, body: body) { result in
switch result {
case .cancelled:
_called(true)
break
case .failed:
self.sentError = .messageFailed
case .sent:
if networkMonitor.connected == false {
self.sentError = .messageNotSent
} else {
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:
_called(true)
break
case .failed:
self.sentError = .messageFailed
case .sent:
if networkMonitor.connected == false {
self.sentError = .messageNotSent
} else {
_called(true)
}
@unknown default:
break
}
@unknown default:
break
}
} else {
SubscriptionView(showLackOfPlanMessage: true)
}
} else {
SubscriptionView(showLackOfPlanMessage: true)
}
<<<<<<< HEAD
=======
.tint(.master)
>>>>>>> 6547a8de016666a19407c6cf8b30e91087f201e2
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
_called(true)
case .failed:
self.contactType = nil
self.sentError = .mailFailed
case .sent:
if networkMonitor.connected == false {
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
self.sentError = .mailNotSent
} else {
_called(true)
case .failed:
self.contactType = nil
self.sentError = .mailFailed
case .sent:
if networkMonitor.connected == false {
self.contactType = nil
self.sentError = .mailNotSent
} else {
_called(true)
}
@unknown default:
break
}
@unknown default:
break
}
} else {
SubscriptionView(showLackOfPlanMessage: true)
}
} else {
SubscriptionView(showLackOfPlanMessage: true)
}
.tint(.master)
}
.tint(.master)
}
}
}

@ -0,0 +1,57 @@
//
// CourtView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 23/04/2024.
//
import SwiftUI
struct CourtView: View {
@EnvironmentObject var dataStore: DataStore
@Bindable var court: Court
@State private var name: String = ""
init(court: Court) {
self.court = court
_name = State(wrappedValue: court.name ?? "")
}
var body: some View {
Form {
Section {
LabeledContent {
TextField("Nom", text: $name)
.keyboardType(.alphabet)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
} label: {
Text("Nom du terrain")
}
}
Section {
Toggle(isOn: $court.exitAllowed) {
Text("Sortie authorisée")
}
Toggle(isOn: $court.indoor) {
Text("Terrain intérieur")
}
}
}
.onChange(of: name) {
court.name = name
try? dataStore.courts.addOrUpdate(instance: court)
}
.onChange(of: [court.indoor, court.exitAllowed]) {
try? dataStore.courts.addOrUpdate(instance: court)
}
.navigationTitle(court.courtTitle())
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
}
#Preview {
CourtView(court: Court.mock())
}

@ -0,0 +1,36 @@
//
// BarButtonView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 24/04/2024.
//
import SwiftUI
struct BarButtonView: View {
let accessibilityLabel: String
let icon: String
let action: () -> ()
init(_ accessibilityLabel: String, icon: String, action: @escaping () -> Void) {
self.accessibilityLabel = accessibilityLabel
self.icon = icon
self.action = action
}
var body: some View {
Button(action: {
action()
}) {
Label {
Text(accessibilityLabel)
} icon: {
Image(systemName: icon)
.resizable()
.scaledToFit()
.frame(minHeight: 28)
}
.labelStyle(.iconOnly)
}
}
}

@ -8,11 +8,21 @@
import SwiftUI
struct FooterButtonView: View {
let title: String
let action: () -> ()
init(_ title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button {
action()
} label: {
Text(title)
.underline()
}
.buttonStyle(.borderless)
}
}
#Preview {
FooterButtonView()
}

@ -42,7 +42,7 @@ struct EventCreationView: View {
if eventType == .approvedTournament {
LabeledContent {
StepperView(count: $duration, minimum: 1, maximum: 3)
StepperView(count: $duration, minimum: 1)
} label: {
Text("Durée")
Text("\(duration) jour" + duration.pluralSuffix)
@ -99,6 +99,7 @@ struct EventCreationView: View {
}
tournaments.forEach { tournament in
tournament.courtCount = selectedClub?.courts.count ?? 2
tournament.startDate = startingDate
tournament.dayDuration = duration
tournament.setupFederalSettings()

@ -29,7 +29,7 @@ struct MatchDetailView: View {
self.match = match
self.matchViewStyle = matchViewStyle
if match.hasStarted() == false && (match.startDate == nil || match.court == nil) {
if match.hasStarted() == false && (match.startDate == nil || match.courtIndex == nil) {
_isEditing = State(wrappedValue: true)
}
@ -44,8 +44,8 @@ struct MatchDetailView: View {
_endDate = State(wrappedValue: endDate)
}
if let court = match.court {
_fieldSetup = State(wrappedValue: .field(court))
if let courtIndex = match.courtIndex {
_fieldSetup = State(wrappedValue: .field(courtIndex))
}
}
@ -57,17 +57,19 @@ struct MatchDetailView: View {
match.removeCourt()
save()
}
ForEach(1...match.courtCount(), id: \.self) { courtIndex in
Button("Terrain #\(courtIndex.formatted())") {
match.setCourt(courtIndex)
save()
if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Button(tournament.courtName(atIndex: courtIndex)) {
match.setCourt(courtIndex)
save()
}
}
}
} label: {
VStack(alignment: .leading) {
Text("terrain").font(.footnote).foregroundStyle(.secondary)
if let court = match.court {
Text("#" + court)
if let courtName = match.courtName() {
Text(courtName)
.foregroundStyle(Color.master)
.underline()
} else {
@ -364,9 +366,10 @@ struct MatchDetailView: View {
Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
ForEach(1...match.courtCount(), id: \.self) { courtIndex in
Text("Terrain #\(courtIndex)")
.tag(MatchFieldSetup.field(String(courtIndex)))
if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
}
} label: {
Text("Choix du terrain")

@ -74,9 +74,9 @@ struct MatchSummaryView: View {
if matchViewStyle == .standardStyle || matchViewStyle == .sectionedStandardStyle
{
Spacer()
if let court = match.court, match.hasEnded() == false {
if let court = match.courtName(), match.hasEnded() == false {
Spacer()
Text("Terrain #\(court)")
Text(court)
}
}
}

@ -56,7 +56,7 @@ struct CourtAvailabilitySettingsView: View {
}
}
} header: {
Text("Terrain #\(key + 1)")
Text(tournament.courtName(atIndex: key))
}
.headerProminence(.increased)
}
@ -84,7 +84,7 @@ struct CourtAvailabilitySettingsView: View {
NavigationStack {
Form {
Section {
CourtPicker(title: "Terrain", selection: $courtIndex, maxCourt: 3)
CourtPicker(title: "Terrain", selection: $courtIndex, maxCourt: tournament.courtCount)
}
Section {
@ -123,6 +123,8 @@ struct CourtAvailabilitySettingsView: View {
}
struct CourtPicker: View {
@Environment(Tournament.self) var tournament: Tournament
let title: String
@Binding var selection: Int
let maxCourt: Int
@ -130,7 +132,7 @@ struct CourtPicker: View {
var body: some View {
Picker(title, selection: $selection) {
ForEach(0..<maxCourt, id: \.self) {
Text("Terrain #\($0 + 1)")
Text(tournament.courtName(atIndex: $0))
}
}
}
@ -139,19 +141,3 @@ struct CourtPicker: View {
#Preview {
CourtAvailabilitySettingsView()
}
/*
LabeledContent {
// switch dayIndex {
// case 1:
// StepperView(count: $dayTwo, maximum: tournament.courtCount)
// case 2:
// StepperView(count: $dayThree, maximum: tournament.courtCount)
// default:
// StepperView(count: $dayOne, maximum: tournament.courtCount)
// }
// } label: {
// Text("Terrains maximum")
// Text(tournament.startDate.formatted(.dateTime.weekday(.wide)) + " + \(dayIndex)")
// }
*/

@ -44,7 +44,7 @@ struct PlanningSettingsView: View {
Section {
DatePicker(tournament.startDate.formatted(.dateTime.weekday()), selection: $tournament.startDate)
LabeledContent {
StepperView(count: $tournament.dayDuration, minimum: 1, maximum: 1_000)
StepperView(count: $tournament.dayDuration, minimum: 1)
} label: {
Text("Durée")
Text("\(tournament.dayDuration) jour" + tournament.dayDuration.pluralSuffix)
@ -56,7 +56,7 @@ struct PlanningSettingsView: View {
}
Section {
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100)
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount)
if tournament.groupStages().isEmpty == false {
TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage())
@ -68,23 +68,6 @@ struct PlanningSettingsView: View {
} label: {
Text("Préciser la disponibilité des terrains")
}
// if tournament.dayDuration > 1 {
// ForEach(0..<tournament.dayDuration, id: \.self) { dayIndex in
// LabeledContent {
// switch dayIndex {
// case 1:
// StepperView(count: $dayTwo, maximum: tournament.courtCount)
// case 2:
// StepperView(count: $dayThree, maximum: tournament.courtCount)
// default:
// StepperView(count: $dayOne, maximum: tournament.courtCount)
// }
// } label: {
// Text("Terrains maximum")
// Text(tournament.startDate.formatted(.dateTime.weekday(.wide)) + " + \(dayIndex)")
// }
// }
// }
}
Section {

@ -36,8 +36,8 @@ struct PlanningView: View {
MatchDetailView(match: match, matchViewStyle: .sectionedStandardStyle)
} label: {
LabeledContent {
if let court = match.court {
Text(court)
if let courtName = match.courtName() {
Text(courtName)
}
} label: {
if let groupStage = match.groupStageObject {

@ -10,7 +10,8 @@ import SwiftUI
struct TournamentClubSettingsView: View {
@Environment(Tournament.self) private var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
@State var selectedCourt: Court?
var body: some View {
@Bindable var tournament = tournament
List {
@ -54,13 +55,60 @@ struct TournamentClubSettingsView: View {
}
Section {
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount, max: 100)
TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount)
}
if let selectedClub {
Section {
ForEach((0..<tournament.courtCount), id: \.self) { courtIndex in
_courtView(atIndex: courtIndex, tournamentClub: selectedClub)
}
}
}
}
.onChange(of: tournament.courtCount) {
try? dataStore.tournaments.addOrUpdate(instance: tournament)
}
.navigationDestination(item: $selectedCourt) { court in
CourtView(court: court)
}
}
@ViewBuilder
private func _courtView(atIndex index: Int, tournamentClub: Club) -> some View {
if let court = tournamentClub.courts.first(where: { $0.index == index }) {
LabeledContent {
if let name = court.name {
Text(name)
}
} label: {
Text(court.courtIndexTitle())
HStack {
if court.indoor {
Text("Couvert")
}
if court.exitAllowed {
Text("Sortie autorisée")
}
}
}
} else {
LabeledContent {
FooterButtonView("personnaliser") {
let court = Court(club: tournamentClub.id, index: index)
try? dataStore.courts.addOrUpdate(instance: court)
selectedCourt = court
}
} label: {
Text(_courtName(atIndex: index))
}
}
}
private func _courtName(atIndex index: Int) -> String {
Court.courtIndexedTitle(atIndex: index)
}
}
#Preview {

@ -13,7 +13,7 @@ struct TournamentDurationManagerView: View {
var body: some View {
@Bindable var tournament = tournament
LabeledContent {
StepperView(count: $tournament.dayDuration, minimum: 1, maximum: 3)
StepperView(count: $tournament.dayDuration, minimum: 1)
} label: {
Text("Durée")
Text("\(tournament.dayDuration) jour" + tournament.dayDuration.pluralSuffix)

@ -10,7 +10,7 @@ import SwiftUI
struct TournamentFieldsManagerView: View {
let localizedStringKey: String
@Binding var count: Int
let max: Int
var max: Int? = nil
var body: some View {
LabeledContent {

@ -22,66 +22,9 @@ struct TournamentView: View {
}
var body: some View {
VStack(spacing: 0.0) {
<<<<<<< HEAD
OffersHeaderView()
=======
// if tournament.missingUnrankedValue() {
// Button("update NC") {
// tournament.femaleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: tournament.rankSourceDate)
// tournament.maleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: tournament.rankSourceDate)
// try? dataStore.tournaments.addOrUpdate(instance: tournament)
// }
// }
//
//
Section {
NavigationLink(value: Screen.inscription) {
LabeledContent {
Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted())
} label: {
Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {
Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened))
}
}
}
if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false {
LabeledContent {
Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened))
} label: {
Text("Date limite")
}
}
} footer: {
if tournament.inscriptionClosed() {
Button {
tournament.lockRegistration()
_save()
} label: {
Text("clôturer les inscriptions")
.underline()
}
.buttonStyle(.borderless)
}
}
>>>>>>> 6547a8de016666a19407c6cf8b30e91087f201e2
List {
// if tournament.missingUnrankedValue() {
// Button("update NC") {
// tournament.femaleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: tournament.rankSourceDate)
// tournament.maleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: tournament.rankSourceDate)
// try? dataStore.tournaments.addOrUpdate(instance: tournament)
// }
// }
//
//
Section {
NavigationLink(value: Screen.inscription) {
LabeledContent {
@ -101,13 +44,17 @@ struct TournamentView: View {
} label: {
Text("Date limite")
}
if endOfInscriptionDate < Date() {
RowButtonView("Clôturer les inscriptions") {
tournament.lockRegistration()
_save()
}
}
} footer: {
if tournament.inscriptionClosed() {
Button {
tournament.lockRegistration()
_save()
} label: {
Text("clôturer les inscriptions")
.underline()
}
.buttonStyle(.borderless)
}
}

Loading…
Cancel
Save