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

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

@ -86,6 +86,11 @@ class Tournament : ModelObject, Storable {
case build 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 { func hasStarted() -> Bool {
startDate <= Date() startDate <= Date()
} }

@ -12,10 +12,12 @@ import TipKit
@main @main
struct PadelClubApp: App { struct PadelClubApp: App {
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
@State private var navigationViewModel = NavigationViewModel()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
MainView() MainView()
.environment(navigationViewModel)
.accentColor(.launchScreenBackground) .accentColor(.launchScreenBackground)
.onAppear { .onAppear {
self._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 { if match.hasEnded() == false {
Menu { Menu {
Button("Non défini") { Button("Non défini") {
match.court = nil match.removeCourt()
save() save()
} }
// ForEach(1...match.numberOfField, id: \.self) { courtIndex in ForEach(1...match.courtCount(), id: \.self) { courtIndex in
// Button("Terrain #\(courtIndex.formatted())") { Button("Terrain #\(courtIndex.formatted())") {
// match.fieldIndex = Int64(courtIndex) match.setCourt(courtIndex)
// save() save()
// } }
// } }
} label: { } label: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("terrain").font(.footnote).foregroundStyle(.secondary) Text("terrain").font(.footnote).foregroundStyle(.secondary)
@ -377,9 +377,10 @@ struct MatchDetailView: View {
if match.isReady() { if match.isReady() {
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) 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("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) Text("À").tag(MatchDateSetup.customDate)
} label: { } label: {
Text("Horaire") Text("Horaire")
@ -414,11 +415,10 @@ struct MatchDetailView: View {
Picker(selection: $fieldSetup) { Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random) Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable) //Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
// ForEach(1...match.numberOfField, id: \.self) { courtIndex in ForEach(1...match.courtCount(), id: \.self) { courtIndex in
// let fieldIndex = Int64(courtIndex) Text("Terrain #\(courtIndex)")
// let fieldIsAvailable : Bool = match.currentTournament?.fieldIsAvailable(fieldIndex) ?? true .tag(MatchFieldSetup.field(String(courtIndex)))
// Label("Terrain #\(courtIndex)", systemImage: match.isFieldPreferred(fieldIndex) ? "heart" : "").tag(MatchFieldSetup.field(courtIndex)) }
// }
} label: { } label: {
Text("Choix du terrain") Text("Choix du terrain")
} }

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

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

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

Loading…
Cancel
Save