add new tab

multistore
Razmig Sarkissian 2 years ago
parent 32d66cc7c4
commit 355833d808
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift
  3. 2
      PadelClub/Data/GroupStage.swift
  4. 2
      PadelClub/Data/Match.swift
  5. 2
      PadelClub/Data/Round.swift
  6. 17
      PadelClub/Data/Tournament.swift
  7. 2
      PadelClub/Manager/PadelRule.swift
  8. 5
      PadelClub/ViewModel/TabDestination.swift
  9. 6
      PadelClub/Views/Navigation/MainView.swift
  10. 35
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  11. 76
      PadelClub/Views/Navigation/PadelClubView.swift
  12. 40
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  13. 35
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  14. 8
      PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift

@ -144,6 +144,7 @@
FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; }; FF5D0D8B2BB4D1E3005CB568 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; };
FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; };
FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; };
FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; };
FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */; }; FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */; };
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; };
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; };
@ -442,6 +443,7 @@
FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; }; FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImportedPlayer+Extensions.swift"; sourceTree = "<group>"; }; FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImportedPlayer+Extensions.swift"; sourceTree = "<group>"; };
FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHolder.swift; sourceTree = "<group>"; }; FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHolder.swift; sourceTree = "<group>"; };
FF5D30552BD95B1100F2B93D /* OngoingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingView.swift; sourceTree = "<group>"; };
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; }; FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; };
FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; }; FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; };
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; }; FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; };
@ -899,6 +901,7 @@
FF3F74FA2B91A04B004CFE0E /* Organizer */, FF3F74FA2B91A04B004CFE0E /* Organizer */,
FF3F74FB2B91A060004CFE0E /* Toolbox */, FF3F74FB2B91A060004CFE0E /* Toolbox */,
FF3F74FC2B91A06B004CFE0E /* Umpire */, FF3F74FC2B91A06B004CFE0E /* Umpire */,
FF5D30542BD95AF600F2B93D /* Ongoing */,
); );
path = Navigation; path = Navigation;
sourceTree = "<group>"; sourceTree = "<group>";
@ -991,6 +994,14 @@
path = ViewModel; path = ViewModel;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FF5D30542BD95AF600F2B93D /* Ongoing */ = {
isa = PBXGroup;
children = (
FF5D30552BD95B1100F2B93D /* OngoingView.swift */,
);
path = Ongoing;
sourceTree = "<group>";
};
FF6EC8FC2B9478C800EA7F5A /* Shared */ = { FF6EC8FC2B9478C800EA7F5A /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1458,6 +1469,7 @@
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */, FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */,
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */, FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */,
FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */,
FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */,
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */, FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */,

@ -92,6 +92,6 @@ extension ImportedPlayer: PlayerHolder {
fileprivate extension Int { fileprivate extension Int {
var femaleInMaleAssimilation: Int { var femaleInMaleAssimilation: Int {
self + TournamentCatgory.femaleInMaleAssimilationAddition(self) self + TournamentCategory.femaleInMaleAssimilationAddition(self)
} }
} }

@ -17,7 +17,7 @@ class GroupStage: ModelObject, Storable {
var tournament: String var tournament: String
var index: Int var index: Int
var size: Int var size: Int
var format: MatchFormat? private var format: MatchFormat?
var startDate: Date? var startDate: Date?
var name: String? var name: String?

@ -19,7 +19,7 @@ class Match: ModelObject, Storable {
var startDate: Date? var startDate: Date?
var endDate: Date? var endDate: Date?
var index: Int var index: Int
var format: MatchFormat? private var format: MatchFormat?
//var court: String? //var court: String?
var servingTeamId: String? var servingTeamId: String?
var winningTeamId: String? var winningTeamId: String?

@ -16,7 +16,7 @@ class Round: ModelObject, Storable {
var tournament: String var tournament: String
var index: Int var index: Int
var parent: String? var parent: String?
var format: MatchFormat? private var format: MatchFormat?
var startDate: Date? var startDate: Date?
internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil) { internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil) {

@ -20,9 +20,9 @@ class Tournament : ModelObject, Storable {
var endDate: Date? var endDate: Date?
private(set) var creationDate: Date private(set) var creationDate: Date
var isPrivate: Bool var isPrivate: Bool
var groupStageFormat: MatchFormat? private var groupStageFormat: MatchFormat?
var roundFormat: MatchFormat? private var roundFormat: MatchFormat?
var loserRoundFormat: MatchFormat? private var loserRoundFormat: MatchFormat?
var groupStageSortMode: GroupStageOrderingMode var groupStageSortMode: GroupStageOrderingMode
var groupStageCount: Int var groupStageCount: Int
var rankSourceDate: Date? var rankSourceDate: Date?
@ -1074,13 +1074,12 @@ class Tournament : ModelObject, Storable {
currentMonthData()?.femaleUnrankedValue currentMonthData()?.femaleUnrankedValue
} }
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
club()?.courts.first(where: { $0.index == courtIndex })?.name
}
func courtName(atIndex courtIndex: Int) -> String { func courtName(atIndex courtIndex: Int) -> String {
let courts = club()?.courts courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
if let courts, let court = courts.first(where: { $0.index == courtIndex }) {
return court.courtTitle()
} else {
return Court.courtIndexedTitle(atIndex: courtIndex)
}
} }
} }

@ -651,7 +651,7 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable {
} }
} }
func femaleInMaleAssimilationAddition(_ rank: Int) -> Int { static func femaleInMaleAssimilationAddition(_ rank: Int) -> Int {
switch rank { switch rank {
case 1...10: return 400 case 1...10: return 400
case 11...30: return 1000 case 11...30: return 1000

@ -18,6 +18,7 @@ enum TabDestination: CaseIterable, Identifiable {
case tournamentOrganizer case tournamentOrganizer
case umpire case umpire
case padelClub case padelClub
case ongoing
var title: String { var title: String {
switch self { switch self {
@ -25,6 +26,8 @@ enum TabDestination: CaseIterable, Identifiable {
return "Activité" return "Activité"
case .eventList: case .eventList:
return "Journal" return "Journal"
case .ongoing:
return "En cours"
case .toolbox: case .toolbox:
return "Outils" return "Outils"
case .tournamentOrganizer: case .tournamentOrganizer:
@ -42,6 +45,8 @@ enum TabDestination: CaseIterable, Identifiable {
return "calendar.day.timeline.left" return "calendar.day.timeline.left"
case .eventList: case .eventList:
return "book.closed" return "book.closed"
case .ongoing:
return "figure.tennis"
case .toolbox: case .toolbox:
return "wrench.and.screwdriver" return "wrench.and.screwdriver"
case .tournamentOrganizer: case .tournamentOrganizer:

@ -34,12 +34,14 @@ struct MainView: View {
.tabItem(for: .activity) .tabItem(for: .activity)
TournamentOrganizerView() TournamentOrganizerView()
.tabItem(for: .tournamentOrganizer) .tabItem(for: .tournamentOrganizer)
OngoingView()
.tabItem(for: .ongoing)
ToolboxView() ToolboxView()
.tabItem(for: .toolbox) .tabItem(for: .toolbox)
UmpireView() UmpireView()
.tabItem(for: .umpire) .tabItem(for: .umpire)
PadelClubView() // PadelClubView()
.tabItem(for: .padelClub) // .tabItem(for: .padelClub)
} }
.environmentObject(dataStore) .environmentObject(dataStore)
.task { .task {

@ -0,0 +1,35 @@
//
// OngoingView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 24/04/2024.
//
import SwiftUI
struct OngoingView: View {
@EnvironmentObject var dataStore: DataStore
var matches: [Match] {
dataStore.matches.filter({ $0.isRunning() }).sorted(by: \.startDate!)
}
var body: some View {
NavigationStack {
List {
ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: .feedStyle)
}
}
.overlay {
ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi."))
}
.navigationTitle("En cours")
.toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar)
}
}
}
#Preview {
OngoingView()
}

@ -37,42 +37,41 @@ struct PadelClubView: View {
} }
var body: some View { var body: some View {
NavigationStack { List {
List { if let _lastDataSourceDate {
if let _lastDataSourceDate { Section {
Section { LabeledContent {
LabeledContent { Image(systemName: "checkmark")
Image(systemName: "checkmark") } label: {
} label: { Text(_lastDataSourceDate.monthYearFormatted)
Text(_lastDataSourceDate.monthYearFormatted) Text("Classement mensuel utilisé")
Text("Classement mensuel utilisé")
}
} }
} }
}
let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed() let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed()
ForEach(monthData) { monthData in ForEach(monthData) { monthData in
Section { Section {
LabeledContent { LabeledContent {
if let maleUnrankedValue = monthData.maleUnrankedValue { if let maleUnrankedValue = monthData.maleUnrankedValue {
Text(maleUnrankedValue.formatted()) Text(maleUnrankedValue.formatted())
}
} label: {
Text("Messieurs")
Text("Rang d'un non classé")
} }
LabeledContent { } label: {
if let femaleUnrankedValue = monthData.femaleUnrankedValue { Text("Messieurs")
Text(femaleUnrankedValue.formatted()) Text("Rang d'un non classé")
} }
} label: { LabeledContent {
Text("Dames") if let femaleUnrankedValue = monthData.femaleUnrankedValue {
Text("Rang d'une non classée") Text(femaleUnrankedValue.formatted())
} }
} header: { } label: {
Text(monthData.monthKey) Text("Dames")
Text("Rang d'une non classée")
} }
} header: {
Text(monthData.monthKey)
} }
}
// //
// if players.isEmpty { // if players.isEmpty {
// ContentUnavailableView { // ContentUnavailableView {
@ -85,18 +84,17 @@ struct PadelClubView: View {
// } // }
// } // }
// } // }
}
.task {
await self._checkSourceFileAvailability()
}
.refreshable {
Task {
await self._checkSourceFileAvailability()
} }
.headerProminence(.increased)
.navigationTitle(TabDestination.padelClub.title)
// .task {
// await self._checkSourceFileAvailability()
// }
// .refreshable {
// Task {
// await self._checkSourceFileAvailability()
// }
// }
} }
.headerProminence(.increased)
.navigationTitle(TabDestination.padelClub.title)
} }
@ViewBuilder @ViewBuilder

@ -10,6 +10,18 @@ import CoreLocation
struct UmpireView: View { struct UmpireView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
var lastDataSource: String? {
dataStore.appSettings.lastDataSource
}
var _mostRecentDateAvailable: Date? {
SourceFileManager.shared.mostRecentDateAvailable
}
var _lastDataSourceDate: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@ -102,6 +114,34 @@ struct UmpireView: View {
} }
} }
Section {
NavigationLink {
PadelClubView()
} label: {
if let _lastDataSourceDate {
LabeledContent {
Image(systemName: "checkmark.circle.fill")
.tint(.green)
} label: {
Text(_lastDataSourceDate.monthYearFormatted)
Text("Classement mensuel utilisé")
}
} else {
LabeledContent {
Image(systemName: "xmark.circle.fill")
.tint(.red)
} label: {
if let _mostRecentDateAvailable {
Text(_mostRecentDateAvailable.monthYearFormatted)
} else {
Text("Aucun")
}
Text("Classement mensuel disponible")
}
}
}
}
// Section { // Section {
// Text("Tenup ID") // Text("Tenup ID")
// } // }

@ -38,8 +38,11 @@ struct CourtAvailabilitySettingsView: View {
Text(dateInterval.startDate.localizedDay()).font(.caption) Text(dateInterval.startDate.localizedDay()).font(.caption)
} }
Spacer() Spacer()
Image(systemName: "arrowshape.forward.fill") VStack {
.tint(.master) Image(systemName: "arrowshape.forward.fill")
.tint(.master)
Text("indisponible").foregroundStyle(.red).font(.caption)
}
Spacer() Spacer()
VStack(alignment: .trailing, spacing: 0) { VStack(alignment: .trailing, spacing: 0) {
Text(dateInterval.endDate.localizedTime()).font(.largeTitle) Text(dateInterval.endDate.localizedTime()).font(.largeTitle)
@ -70,27 +73,39 @@ struct CourtAvailabilitySettingsView: View {
} }
} }
} header: { } header: {
Text(tournament.courtName(atIndex: key)) HStack {
Text(Court.courtIndexedTitle(atIndex: key))
Spacer()
if let courtName = tournament.courtNameIfAvailable(atIndex: key) {
Text(courtName)
}
}
} }
.headerProminence(.increased) .headerProminence(.increased)
} }
} }
} }
.overlay {
ContentUnavailableView {
Label("Tous les terrains sont disponibles", systemImage: "checkmark.circle.fill")
} description: {
Text("Vous pouvez précisez l'indisponibilité d'une ou plusieurs terrains, que ce soit pour une journée entière ou un créneau précis.")
} actions: {
RowButtonView("Ajouter une indisponibilité", systemImage: "plus.circle.fill") {
showingPopover = true
}
}
}
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button { BarButtonView("Ajouter une indisponibilité", icon: "plus.circle.fill") {
showingPopover = true showingPopover = true
} label: {
Image(systemName: "plus.circle.fill")
.resizable()
.scaledToFit()
.frame(minHeight: 28)
} }
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Créneaux") .navigationTitle("Créneau indisponible")
.popover(isPresented: $showingPopover) { .popover(isPresented: $showingPopover) {
NavigationStack { NavigationStack {
Form { Form {

@ -41,12 +41,12 @@ struct TournamentMatchFormatsSettingsView: View {
Label("Définir les durées moyennes", systemImage: "deskclock") Label("Définir les durées moyennes", systemImage: "deskclock")
} }
} footer: { } footer: {
Text("Vous pouvez définir vos propores estimations de durées de match en fonction du format de jeu.") Text("Vous pouvez définir vos propres estimations de durées de match en fonction du format de jeu.")
} }
} }
.onChange(of: [tournament.roundFormat, .onChange(of: [tournament.loserBracketMatchFormat,
tournament.groupStageFormat, tournament.groupStageMatchFormat,
tournament.loserRoundFormat, tournament.matchFormat,
]) { ]) {
_save() _save()
_confirmOrSave() _confirmOrSave()

Loading…
Cancel
Save