add court management

multistore
Razmig Sarkissian 2 years ago
parent 992d7fb744
commit ff0b236afc
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 51
      PadelClub/Data/Match.swift
  3. 5
      PadelClub/Data/Tournament.swift
  4. 4
      PadelClub/PadelClubApp.swift
  5. 14
      PadelClub/ViewModel/NavigationViewModel.swift
  6. 26
      PadelClub/Views/Match/MatchDetailView.swift
  7. 2
      PadelClub/Views/Match/MatchSummaryView.swift
  8. 29
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  9. 17
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -173,6 +173,7 @@
FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; };
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; };
FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; };
FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.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 */; };
@ -416,6 +417,7 @@
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = "<group>"; };
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; };
FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = "<group>"; };
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.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>"; };
@ -812,6 +814,7 @@
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */,
FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */,
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */,
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -1275,6 +1278,7 @@
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */,
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */,
FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */,
FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */,
FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */,
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */,
FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */,

@ -171,22 +171,49 @@ class Match: ModelObject, Storable {
func validateMatch(fromStartDate: Date, toEndDate: Date, fieldSetup: MatchFieldSetup) {
if hasEnded() == false {
startDate = fromStartDate
// if match.isTournamentMatch() {
// switch fieldSetup {
// case .random:
// let field = match.freeFields.randomElement() ?? match.currentTournament?.freeFields.randomElement() ?? 1
// match.setupFieldAndStartDateIfPossible(field)
// case .field(let courtIndex):
// let fieldIndex = Int64(courtIndex)
// match.setupFieldAndStartDateIfPossible(fieldIndex)
// }
// }
switch fieldSetup {
case .random:
let courtName = availableCourts().randomElement()
court = courtName
case .field(let courtName):
court = courtName
}
} else {
startDate = fromStartDate
endDate = toEndDate
}
}
func courtCount() -> Int {
currentTournament()?.courtCount ?? 1
}
func courtIsAvailable(_ courtIndex: Int) -> Bool {
let courtUsed = currentTournament()?.courtUsed() ?? []
return courtUsed.contains(String(courtIndex)) == false
// return Set(availableCourts().map { String($0) }).subtracting(Set(courtUsed))
}
func courtIsPreferred(_ courtIndex: Int) -> Bool {
false
}
func availableCourts() -> [String] {
let courtUsed = currentTournament()?.courtUsed() ?? []
let availableCourts = Array(1...courtCount())
return Array(Set(availableCourts.map { String($0) }).subtracting(Set(courtUsed)))
}
func removeCourt() {
court = nil
}
func setCourt(_ courtIndex: Int) {
court = String(courtIndex)
}
func canBeStarted() -> Bool {
let teams = teams()
guard teams.count == 2 else { return false }
@ -241,6 +268,10 @@ class Match: ModelObject, Storable {
groupStageObject?.tournamentObject() ?? roundObject?.tournamentObject()
}
func tournamentId() -> String? {
groupStageObject?.tournament ?? roundObject?.tournament
}
func scores() -> [TeamScore] {
Store.main.filter(isIncluded: { $0.match == id })
}

@ -86,6 +86,11 @@ class Tournament : ModelObject, Storable {
case build
}
func courtUsed() -> [String] {
let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id })
return Set(runningMatches.compactMap { $0.court }).sorted()
}
func hasStarted() -> Bool {
startDate <= Date()
}

@ -12,10 +12,12 @@ import TipKit
@main
struct PadelClubApp: App {
let persistenceController = PersistenceController.shared
@State private var navigationViewModel = NavigationViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environment(navigationViewModel)
.accentColor(.launchScreenBackground)
.onAppear {
self._onAppear()

@ -0,0 +1,14 @@
//
// NavigationViewModel.swift
// PadelClub
//
// Created by Razmig Sarkissian on 03/04/2024.
//
import SwiftUI
@Observable
class NavigationViewModel {
var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament?
}

@ -103,15 +103,15 @@ struct MatchDetailView: View {
if match.hasEnded() == false {
Menu {
Button("Non défini") {
match.court = nil
match.removeCourt()
save()
}
// ForEach(1...match.numberOfField, id: \.self) { courtIndex in
// Button("Terrain #\(courtIndex.formatted())") {
// match.fieldIndex = Int64(courtIndex)
// save()
// }
// }
ForEach(1...match.courtCount(), id: \.self) { courtIndex in
Button("Terrain #\(courtIndex.formatted())") {
match.setCourt(courtIndex)
save()
}
}
} label: {
VStack(alignment: .leading) {
Text("terrain").font(.footnote).foregroundStyle(.secondary)
@ -377,9 +377,10 @@ struct MatchDetailView: View {
if match.isReady() {
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15))
Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(match.matchFormat.estimatedDuration))
Text("Tout de suite").tag(MatchDateSetup.now)
}
Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-match.matchFormat.estimatedDuration))
Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(match.matchFormat.estimatedDuration))
Text("À").tag(MatchDateSetup.customDate)
} label: {
Text("Horaire")
@ -414,11 +415,10 @@ struct MatchDetailView: View {
Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
// ForEach(1...match.numberOfField, id: \.self) { courtIndex in
// let fieldIndex = Int64(courtIndex)
// let fieldIsAvailable : Bool = match.currentTournament?.fieldIsAvailable(fieldIndex) ?? true
// Label("Terrain #\(courtIndex)", systemImage: match.isFieldPreferred(fieldIndex) ? "heart" : "").tag(MatchFieldSetup.field(courtIndex))
// }
ForEach(1...match.courtCount(), id: \.self) { courtIndex in
Text("Terrain #\(courtIndex)")
.tag(MatchFieldSetup.field(String(courtIndex)))
}
} label: {
Text("Choix du terrain")
}

@ -74,7 +74,7 @@ struct MatchSummaryView: View {
Spacer()
if let court = match.court, match.hasEnded() == false {
Spacer()
Text("Terrain \(court)")
Text("Terrain #\(court)")
}
}
}

@ -9,9 +9,9 @@ import SwiftUI
struct ActivityView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation
@State private var searchText: String = ""
@State private var agendaDestination: AgendaDestination? = .activity
@State private var filterEnabled: Bool = false
@State private var presentToolbar: Bool = false
@ -41,7 +41,7 @@ struct ActivityView: View {
}
var tournaments: [FederalTournamentHolder] {
switch agendaDestination! {
switch navigation.agendaDestination! {
case .activity:
runningTournaments
case .history:
@ -53,10 +53,11 @@ struct ActivityView: View {
var body: some View {
NavigationStack {
@Bindable var navigation = navigation
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false)
GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false)
List {
switch agendaDestination! {
switch navigation.agendaDestination! {
case .activity:
EventListView(tournaments: runningTournaments, viewStyle: viewStyle)
case .history:
@ -66,7 +67,7 @@ struct ActivityView: View {
}
}
.overlay {
if let error, agendaDestination == .tenup {
if let error, navigation.agendaDestination == .tenup {
ContentUnavailableView {
Label("Erreur", systemImage: "exclamationmark")
} description: {
@ -105,20 +106,20 @@ struct ActivityView: View {
EventCreationView(tournaments: [tournament])
}
.refreshable {
if agendaDestination == .tenup {
if navigation.agendaDestination == .tenup {
federalTournaments.removeAll()
_gatherFederalTournaments()
}
}
.task {
if agendaDestination == .tenup
if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.onChange(of: agendaDestination) {
if agendaDestination == .tenup
.onChange(of: navigation.agendaDestination) {
if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& federalTournaments.isEmpty {
_gatherFederalTournaments()
@ -186,6 +187,10 @@ struct ActivityView: View {
TournamentView()
.environment(tournament)
}
.navigationDestination(item: $navigation.tournament) { tournament in
TournamentView()
.environment(tournament)
}
}
}
}
@ -206,7 +211,7 @@ struct ActivityView: View {
@ViewBuilder
private func _dataEmptyView() -> some View {
switch agendaDestination! {
switch navigation.agendaDestination! {
case .activity:
_runningEmptyView()
case .history:
@ -234,7 +239,7 @@ struct ActivityView: View {
newTournament = Tournament.newEmptyInstance()
}
RowButtonView("Importer via Tenup") {
agendaDestination = .tenup
navigation.agendaDestination = .tenup
}
}
}

@ -9,6 +9,7 @@ import SwiftUI
struct TournamentCellView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation
let tournament: FederalTournamentHolder
let color: Color = .black
@ -21,11 +22,11 @@ struct TournamentCellView: View {
var body: some View {
ForEach(tournament.tournaments, id: \.id) { build in
_buildView(build, alreadyExist: event?.existingBuild(build) != nil)
_buildView(build, existingTournament: event?.existingBuild(build))
}
}
private func _buildView(_ build: any TournamentBuildHolder, alreadyExist: Bool) -> some View {
private func _buildView(_ build: any TournamentBuildHolder, existingTournament: Tournament?) -> some View {
HStack {
DateBoxView(date: tournament.startDate, displayStyle: displayStyle)
Rectangle()
@ -53,7 +54,10 @@ struct TournamentCellView: View {
Text(tournament.sortedTeams().count.formatted())
} else if let federalTournament = tournament as? FederalTournament {
Button {
if alreadyExist == false {
if let existingTournament {
navigation.agendaDestination = .activity
navigation.tournament = existingTournament
} else {
let event = federalTournament.getEvent()
let newTournament = Tournament.newEmptyInstance()
newTournament.event = event.id
@ -63,17 +67,14 @@ struct TournamentCellView: View {
newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate
try? dataStore.tournaments.addOrUpdate(instance: newTournament)
} else {
//event?.existingBuild(build)
}
} label: {
Image(systemName: alreadyExist ? "checkmark.circle.fill" : "square.and.arrow.down")
Image(systemName: existingTournament != nil ? "checkmark.circle.fill" : "square.and.arrow.down")
.resizable()
.scaledToFit()
.frame(height: 28)
.tint(alreadyExist ? Color.green : nil)
.tint(existingTournament != nil ? Color.green : nil)
}
.buttonStyle(.borderless)
}
}
.font(displayStyle == .wide ? .title : .title3)

Loading…
Cancel
Save