multistore
Razmig Sarkissian 2 years ago
parent 348aa591f3
commit c0eb74bb43
  1. 6
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Data/DataStore.swift
  3. 56
      PadelClub/Data/DateInterval.swift
  4. 6
      PadelClub/Data/Event.swift
  5. 1
      PadelClub/Data/GroupStage.swift
  6. 6
      PadelClub/Data/MockData.swift
  7. 2
      PadelClub/Data/Tournament.swift
  8. 32
      PadelClub/ViewModel/DateInterval.swift
  9. 7
      PadelClub/ViewModel/MatchScheduler.swift
  10. 4
      PadelClub/Views/Club/CreateClubView.swift
  11. 22
      PadelClub/Views/Components/ButtonValidateView.swift
  12. 29
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  13. 35
      PadelClub/Views/GroupStage/GroupStageView.swift
  14. 1
      PadelClub/Views/GroupStage/GroupStagesView.swift
  15. 48
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  16. 14
      PadelClub/Views/Planning/PlanningSettingsView.swift
  17. 4
      PadelClub/Views/Player/Components/PlayerPopoverView.swift
  18. 6
      PadelClub/Views/Tournament/FileImportView.swift
  19. 9
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  20. 2
      PadelClub/Views/Tournament/TournamentView.swift

@ -238,6 +238,7 @@
FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; };
FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; };
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; };
FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; };
FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; };
FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; };
FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; }; FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; };
@ -534,6 +535,7 @@
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; }; FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; }; FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonValidateView.swift; sourceTree = "<group>"; };
FFF116E02BD2A9B600A33B06 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = "<group>"; }; FFF116E02BD2A9B600A33B06 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = "<group>"; };
FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtAvailabilitySettingsView.swift; sourceTree = "<group>"; }; FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtAvailabilitySettingsView.swift; sourceTree = "<group>"; };
FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduleEditorView.swift; sourceTree = "<group>"; }; FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduleEditorView.swift; sourceTree = "<group>"; };
@ -678,6 +680,7 @@
FF1DC5522BAB354A00FD8220 /* MockData.swift */, FF1DC5522BAB354A00FD8220 /* MockData.swift */,
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */, FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */,
FFC91B002BD85C2F00B29808 /* Court.swift */, FFC91B002BD85C2F00B29808 /* Court.swift */,
FFF116E02BD2A9B600A33B06 /* DateInterval.swift */,
FF6EC9012B94799200EA7F5A /* Coredata */, FF6EC9012B94799200EA7F5A /* Coredata */,
FF6EC9022B9479B900EA7F5A /* Federal */, FF6EC9022B9479B900EA7F5A /* Federal */,
); );
@ -756,6 +759,7 @@
FF967CF72BAEDF0000A9A3BD /* Labels.swift */, FF967CF72BAEDF0000A9A3BD /* Labels.swift */,
FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */, FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */,
FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */, FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */,
FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -979,7 +983,6 @@
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */, FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */,
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */, FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */,
FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */, FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */,
FFF116E02BD2A9B600A33B06 /* DateInterval.swift */,
); );
path = ViewModel; path = ViewModel;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1503,6 +1506,7 @@
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */, FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */, FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */,
FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */,
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */, FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */, FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
FF9268032BCE94A30080F940 /* GroupStageCallingView.swift in Sources */, FF9268032BCE94A30080F940 /* GroupStageCallingView.swift in Sources */,

@ -26,7 +26,8 @@ class DataStore: ObservableObject {
fileprivate(set) var rounds: StoredCollection<Round> fileprivate(set) var rounds: StoredCollection<Round>
fileprivate(set) var teamScores: StoredCollection<TeamScore> fileprivate(set) var teamScores: StoredCollection<TeamScore>
fileprivate(set) var monthData: StoredCollection<MonthData> fileprivate(set) var monthData: StoredCollection<MonthData>
fileprivate(set) var dateIntervals: StoredCollection<DateInterval>
fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json") fileprivate var _userStorage: OptionalStorage<User> = OptionalStorage<User>(fileName: "user.json")
fileprivate var _appSettingsStorage: MicroStorage<AppSettings> = MicroStorage() fileprivate var _appSettingsStorage: MicroStorage<AppSettings> = MicroStorage()
@ -78,6 +79,7 @@ class DataStore: ObservableObject {
self.rounds = store.registerCollection(synchronized: false, indexed: indexed) self.rounds = store.registerCollection(synchronized: false, indexed: indexed)
self.matches = store.registerCollection(synchronized: false, indexed: indexed) self.matches = store.registerCollection(synchronized: false, indexed: indexed)
self.monthData = store.registerCollection(synchronized: false, indexed: indexed) self.monthData = store.registerCollection(synchronized: false, indexed: indexed)
self.dateIntervals = store.registerCollection(synchronized: false, indexed: indexed)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil) 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) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil)

@ -0,0 +1,56 @@
//
// DateInterval.swift
// PadelClub
//
// Created by Razmig Sarkissian on 19/04/2024.
//
import Foundation
import SwiftUI
import LeStorage
@Observable
class DateInterval: ModelObject, Storable {
static func resourceName() -> String { return "date-intervals" }
var id: String = Store.randomId()
var event: String
var courtIndex: Int
var startDate: Date
var endDate: Date
internal init(event: String, courtIndex: Int, startDate: Date, endDate: Date) {
self.event = event
self.courtIndex = courtIndex
self.startDate = startDate
self.endDate = endDate
}
var range: Range<Date> {
startDate..<endDate
}
func isSingleDay() -> Bool {
Calendar.current.isDate(startDate, inSameDayAs: endDate)
}
func isDateInside(_ date: Date) -> Bool {
date >= startDate && date <= endDate
}
func isDateOutside(_ date: Date) -> Bool {
date <= startDate && date <= endDate && date >= startDate && date >= endDate
}
override func deleteDependencies() throws {
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _event = "event"
case _courtIndex = "courtIndex"
case _startDate = "startDate"
case _endDate = "endDate"
}
}

@ -47,8 +47,14 @@ class Event: ModelObject, Storable {
tournaments.first(where: { $0.isSameBuild(build) }) tournaments.first(where: { $0.isSameBuild(build) })
} }
var courtsUnavailability: [DateInterval] {
Store.main.filter(isIncluded: { $0.event == id })
}
override func deleteDependencies() throws { override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.tournaments) try Store.main.deleteDependencies(items: self.tournaments)
try Store.main.deleteDependencies(items: self.courtsUnavailability)
} }
} }

@ -47,6 +47,7 @@ class GroupStage: ModelObject, Storable {
} }
func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String {
if let name { return "Poule " + name }
switch displayStyle { switch displayStyle {
case .wide: case .wide:
return "Poule \(index + 1)" return "Poule \(index + 1)"

@ -13,6 +13,12 @@ extension Court {
} }
} }
extension Event {
static func mock() -> Event {
Event()
}
}
extension Club { extension Club {
static func mock() -> Club { static func mock() -> Club {
Club(name: "AUC", acronym: "AUC") Club(name: "AUC", acronym: "AUC")

@ -43,8 +43,6 @@ class Tournament : ModelObject, Storable {
var entryFee: Double? var entryFee: Double?
var payment: TournamentPayment? = nil var payment: TournamentPayment? = nil
var additionalEstimationDuration: Int = 0 var additionalEstimationDuration: Int = 0
var courtsUnavailability: [Int: [DateInterval]]? = nil
@ObservationIgnored @ObservationIgnored
var navigationPath: [Screen] = [] var navigationPath: [Screen] = []

@ -1,32 +0,0 @@
//
// DateInterval.swift
// PadelClub
//
// Created by Razmig Sarkissian on 19/04/2024.
//
import Foundation
import LeStorage
struct DateInterval: Identifiable, Codable {
var id: String = Store.randomId()
let startDate: Date
let endDate: Date
var range: Range<Date> {
startDate..<endDate
}
func isSingleDay() -> Bool {
Calendar.current.isDate(startDate, inSameDayAs: endDate)
}
func isDateInside(_ date: Date) -> Bool {
date >= startDate && date <= endDate
}
func isDateOutside(_ date: Date) -> Bool {
date <= startDate && date <= endDate && date >= startDate && date >= endDate
}
}

@ -67,7 +67,7 @@ class MatchScheduler {
var timeDifferenceLimit: Double = 300.0 var timeDifferenceLimit: Double = 300.0
var loserBracketRotationDifference: Int = 0 var loserBracketRotationDifference: Int = 0
var upperBracketRotationDifference: Int = 1 var upperBracketRotationDifference: Int = 1
var courtsUnavailability: [Int: [DateInterval]]? = nil var courtsUnavailability: [DateInterval]? = nil
func shouldHandleUpperRoundSlice() -> Bool { func shouldHandleUpperRoundSlice() -> Bool {
options.contains(.shouldHandleUpperRoundSlice) options.contains(.shouldHandleUpperRoundSlice)
@ -522,14 +522,15 @@ class MatchScheduler {
func courtsUnavailable(startDate: Date, duration: Int) -> Int { func courtsUnavailable(startDate: Date, duration: Int) -> Int {
let endDate = startDate.addingTimeInterval(Double(duration) * 60) let endDate = startDate.addingTimeInterval(Double(duration) * 60)
guard let courtsUnavailability else { return 0 } guard let courtsUnavailability else { return 0 }
let courts = courtsUnavailability.keys let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex })
let courts = groupedBy.keys
return courts.filter { return courts.filter {
courtUnavailable(courtIndex: $0, from: startDate, to: endDate) courtUnavailable(courtIndex: $0, from: startDate, to: endDate)
}.count }.count
} }
func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool { func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool {
guard let courtLockedSchedule = courtsUnavailability?[courtIndex] else { return true } guard let courtLockedSchedule = courtsUnavailability?.filter({ $0.courtIndex == courtIndex }) else { return true }
return courtLockedSchedule.anySatisfy({ dateInterval in return courtLockedSchedule.anySatisfy({ dateInterval in
dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate) dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate)
}) })

@ -27,12 +27,10 @@ struct CreateClubView: View {
} }
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
Button("Valider") { ButtonValidateView {
try? dataStore.clubs.addOrUpdate(instance: club) try? dataStore.clubs.addOrUpdate(instance: club)
dismiss() dismiss()
} }
.clipShape(Capsule())
.buttonStyle(.bordered)
.disabled(club.isValid == false) .disabled(club.isValid == false)
} }
} }

@ -0,0 +1,22 @@
//
// ButtonValidateView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 24/04/2024.
//
import SwiftUI
struct ButtonValidateView: View {
var role: ButtonRole?
let action: () -> ()
var body: some View {
Button("Valider", role: role) {
action()
}
.clipShape(Capsule())
.buttonStyle(.bordered)
}
}

@ -8,10 +8,26 @@
import SwiftUI import SwiftUI
struct GroupStageSettingsView: View { struct GroupStageSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@State private var nameAlphabetical: Bool = false
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
private func _letterForIndex(index: Int) -> String? {
let uppercaseAlphabet = Array(alphabet)
if let letter = uppercaseAlphabet[safe: index] {
return String(letter)
} else {
return nil
}
}
var body: some View { var body: some View {
List { List {
Toggle(isOn: $nameAlphabetical) {
Text("Nommer les poules alphabétiquement")
}
Menu { Menu {
//menuAddGroupStage //menuAddGroupStage
menuBuildAllGroupStages menuBuildAllGroupStages
@ -146,6 +162,19 @@ struct GroupStageSettingsView: View {
} }
.onChange(of: nameAlphabetical) {
let groupStages = tournament.groupStages()
if nameAlphabetical {
groupStages.forEach { groupStage in
groupStage.name = _letterForIndex(index: groupStage.index)
}
} else {
groupStages.forEach { groupStage in
groupStage.name = nil
}
}
try? dataStore.groupStages.addOrUpdate(contentOfs: groupStages)
}
} }

@ -14,7 +14,8 @@ struct GroupStageView: View {
@State private var sortingMode: GroupStageSortingMode = .auto @State private var sortingMode: GroupStageSortingMode = .auto
@State private var confirmRemoveAll: Bool = false @State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false @State private var confirmResetMatch: Bool = false
@State private var groupStageName: String = ""
private enum GroupStageSortingMode { private enum GroupStageSortingMode {
case auto case auto
case score case score
@ -29,6 +30,11 @@ struct GroupStageView: View {
sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index) sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index)
} }
init(groupStage: GroupStage) {
self.groupStage = groupStage
_groupStageName = State(wrappedValue: groupStage.groupStageTitle())
}
var body: some View { var body: some View {
List { List {
Section { Section {
@ -66,11 +72,16 @@ struct GroupStageView: View {
MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID()) MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID())
MatchListView(section: "terminés", matches: groupStage.finishedMatches()).id(UUID()) MatchListView(section: "terminés", matches: groupStage.finishedMatches()).id(UUID())
} }
.onChange(of: groupStageName) {
groupStage.name = groupStageName
try? dataStore.groupStages.addOrUpdate(instance: groupStage)
}
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
_groupStageMenuView() _groupStageMenuView()
} }
} }
.navigationTitle($groupStageName)
} }
private func _groupStageView() -> some View { private func _groupStageView() -> some View {
@ -129,15 +140,23 @@ struct GroupStageView: View {
private func _groupStageMenuView() -> some View { private func _groupStageMenuView() -> some View {
Menu { Menu {
if groupStage.playedMatches().isEmpty { if groupStage.name != nil {
Button { Button("Retirer le nom") {
//groupStage.startGroupStage() groupStage.name = nil
//save() groupStageName = groupStage.groupStageTitle()
} label: { try? dataStore.groupStages.addOrUpdate(instance: groupStage)
Text("Créer les matchs")
} }
.buttonStyle(.borderless)
} }
// if groupStage.playedMatches().isEmpty {
// Button {
// //groupStage.startGroupStage()
// //save()
// } label: {
// Text("Créer les matchs")
// }
// .buttonStyle(.borderless)
// }
Button("Retirer tout le monde", role: .destructive) { Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true confirmRemoveAll = true

@ -81,7 +81,6 @@ struct GroupStagesView: View {
.navigationTitle("Toutes les poules") .navigationTitle("Toutes les poules")
case .groupStage(let groupStage): case .groupStage(let groupStage):
GroupStageView(groupStage: groupStage) GroupStageView(groupStage: groupStage)
.navigationTitle(groupStage.groupStageTitle())
case nil: case nil:
GroupStageSettingsView() GroupStageSettingsView()
.navigationTitle("Réglages") .navigationTitle("Réglages")

@ -9,15 +9,25 @@ import SwiftUI
struct CourtAvailabilitySettingsView: View { struct CourtAvailabilitySettingsView: View {
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@State private var courtsUnavailability: [Int: [DateInterval]] = [Int:[DateInterval]]() @EnvironmentObject var dataStore: DataStore
let event: Event
@State private var showingPopover: Bool = false @State private var showingPopover: Bool = false
@State private var courtIndex: Int = 0 @State private var courtIndex: Int = 0
@State private var startDate: Date = Date() @State private var startDate: Date = Date()
@State private var endDate: Date = Date() @State private var endDate: Date = Date()
var courtsUnavailability: [Int: [DateInterval]] {
let groupedBy = Dictionary(grouping: event.courtsUnavailability, by: { dateInterval in
return dateInterval.courtIndex
})
return groupedBy
}
var body: some View { var body: some View {
List { List {
let keys = courtsUnavailability.keys.sorted(by: \.self) let keys = courtsUnavailability.keys.sorted()
ForEach(keys, id: \.self) { key in ForEach(keys, id: \.self) { key in
if let dates = courtsUnavailability[key] { if let dates = courtsUnavailability[key] {
Section { Section {
@ -38,18 +48,22 @@ struct CourtAvailabilitySettingsView: View {
} }
.contextMenu(menuItems: { .contextMenu(menuItems: {
Button("dupliquer") { Button("dupliquer") {
let duplicatedDateInterval = DateInterval(event: event.id, courtIndex: (courtIndex+1)%tournament.courtCount, startDate: dateInterval.startDate, endDate: dateInterval.endDate)
try? dataStore.dateIntervals.addOrUpdate(instance: duplicatedDateInterval)
} }
Button("éditer") { Button("éditer") {
courtIndex = dateInterval.courtIndex
startDate = dateInterval.startDate
endDate = dateInterval.endDate
showingPopover = true
} }
Button("effacer") { Button("effacer", role: .destructive) {
try? dataStore.dateIntervals.delete(instance: dateInterval)
} }
}) })
.swipeActions { .swipeActions {
Button(role: .destructive) { Button(role: .destructive) {
courtsUnavailability[key]?.removeAll(where: { $0.id == dateInterval.id }) try? dataStore.dateIntervals.delete(instance: dateInterval)
} label: { } label: {
LabelDelete() LabelDelete()
} }
@ -74,9 +88,6 @@ struct CourtAvailabilitySettingsView: View {
} }
} }
} }
.onDisappear {
tournament.courtsUnavailability = courtsUnavailability
}
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Créneaux") .navigationTitle("Créneaux")
@ -91,26 +102,23 @@ struct CourtAvailabilitySettingsView: View {
DatePicker("Début", selection: $startDate) DatePicker("Début", selection: $startDate)
DatePicker("Fin", selection: $endDate) DatePicker("Fin", selection: $endDate)
} footer: { } footer: {
Button("jour entier") { FooterButtonView("jour entier") {
startDate = startDate.startOfDay startDate = startDate.startOfDay
endDate = endDate.endOfDay() endDate = endDate.endOfDay()
} }
.buttonStyle(.borderless)
.underline()
} }
} }
.toolbar { .toolbar {
Button("Valider") { ButtonValidateView {
let dateInterval = DateInterval(startDate: startDate, endDate: endDate) let dateInterval = DateInterval(event: event.id, courtIndex: courtIndex, startDate: startDate, endDate: endDate)
var courtUnavailability = courtsUnavailability[courtIndex] ?? [DateInterval]() try? dataStore.dateIntervals.addOrUpdate(instance: dateInterval)
courtUnavailability.append(dateInterval)
courtsUnavailability[courtIndex] = courtUnavailability
showingPopover = false showingPopover = false
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Nouveau créneau") .navigationTitle("Nouveau créneau")
.tint(.master)
} }
.onAppear { .onAppear {
UIDatePicker.appearance().minuteInterval = 5 UIDatePicker.appearance().minuteInterval = 5
@ -139,5 +147,5 @@ struct CourtPicker: View {
} }
#Preview { #Preview {
CourtAvailabilitySettingsView() CourtAvailabilitySettingsView(event: Event.mock())
} }

@ -62,11 +62,13 @@ struct PlanningSettingsView: View {
TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage()) TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage())
} }
NavigationLink { if let event = tournament.eventObject {
CourtAvailabilitySettingsView() NavigationLink {
.environment(tournament) CourtAvailabilitySettingsView(event: event)
} label: { .environment(tournament)
Text("Préciser la disponibilité des terrains") } label: {
Text("Préciser la disponibilité des terrains")
}
} }
} }
@ -176,7 +178,7 @@ struct PlanningSettingsView: View {
let groupStages = tournament.groupStages() let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
let matchScheduler = MatchScheduler.shared let matchScheduler = MatchScheduler.shared
matchScheduler.courtsUnavailability = tournament.courtsUnavailability matchScheduler.courtsUnavailability = tournament.eventObject?.courtsUnavailability
matchScheduler.options.removeAll() matchScheduler.options.removeAll()
if randomCourtDistribution { if randomCourtDistribution {

@ -186,12 +186,10 @@ struct PlayerPopoverView: View {
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar { .toolbar {
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
Button("Valider") { ButtonValidateView {
createManualPlayer() createManualPlayer()
dismiss() dismiss()
} }
.clipShape(Capsule())
.buttonStyle(.bordered)
.disabled(isPlayerValid() == false) .disabled(isPlayerValid() == false)
} }

@ -223,7 +223,7 @@ struct FileImportView: View {
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button { ButtonValidateView {
if false { //selectedOptions.contains(.deleteBeforeImport) if false { //selectedOptions.contains(.deleteBeforeImport)
try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams()) try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams())
} }
@ -245,11 +245,7 @@ struct FileImportView: View {
tournament.importTeams(filteredTeams) tournament.importTeams(filteredTeams)
dismiss() dismiss()
} label: {
Text("Valider")
} }
.buttonStyle(.bordered)
.clipShape(Capsule())
.disabled(teams.isEmpty) .disabled(teams.isEmpty)
} }
} }

@ -191,23 +191,18 @@ struct TableStructureView: View {
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
if tournament.state() == .initial { if tournament.state() == .initial {
Button("Valider") { ButtonValidateView {
_save(rebuildEverything: true) _save(rebuildEverything: true)
} }
.clipShape(Capsule())
.buttonStyle(.bordered)
} else { } else {
let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding }) let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding })
ButtonValidateView(role: .destructive) {
Button("Valider", role: .destructive) {
if requirements.isEmpty { if requirements.isEmpty {
_save(rebuildEverything: false) _save(rebuildEverything: false)
} else { } else {
presentRefreshStructureWarning = true presentRefreshStructureWarning = true
} }
} }
.clipShape(Capsule())
.buttonStyle(.bordered)
.disabled(updatedElements.isEmpty) .disabled(updatedElements.isEmpty)
.confirmationDialog("Mise à jour de la structure", isPresented: $presentRefreshStructureWarning, actions: { .confirmationDialog("Mise à jour de la structure", isPresented: $presentRefreshStructureWarning, actions: {

@ -29,7 +29,6 @@ struct TournamentView: View {
NavigationLink(value: Screen.inscription) { NavigationLink(value: Screen.inscription) {
LabeledContent { LabeledContent {
Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted())
.foregroundStyle(.master)
} label: { } label: {
Text("Gestion des inscriptions") Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate { if let closedRegistrationDate = tournament.closedRegistrationDate {
@ -40,7 +39,6 @@ struct TournamentView: View {
if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false { if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false {
LabeledContent { LabeledContent {
Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened)) Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened))
.foregroundStyle(.master)
} label: { } label: {
Text("Date limite") Text("Date limite")
} }

Loading…
Cancel
Save