add new tab

multistore
Razmig Sarkissian 2 years ago
parent 32d66cc7c4
commit 355833d808
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      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. 78
      PadelClub/Views/Navigation/PadelClubView.swift
  12. 42
      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 */; };
FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.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 */; };
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.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>"; };
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>"; };
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>"; };
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>"; };
@ -899,6 +901,7 @@
FF3F74FA2B91A04B004CFE0E /* Organizer */,
FF3F74FB2B91A060004CFE0E /* Toolbox */,
FF3F74FC2B91A06B004CFE0E /* Umpire */,
FF5D30542BD95AF600F2B93D /* Ongoing */,
);
path = Navigation;
sourceTree = "<group>";
@ -991,6 +994,14 @@
path = ViewModel;
sourceTree = "<group>";
};
FF5D30542BD95AF600F2B93D /* Ongoing */ = {
isa = PBXGroup;
children = (
FF5D30552BD95B1100F2B93D /* OngoingView.swift */,
);
path = Ongoing;
sourceTree = "<group>";
};
FF6EC8FC2B9478C800EA7F5A /* Shared */ = {
isa = PBXGroup;
children = (
@ -1458,6 +1469,7 @@
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */,
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */,
FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */,
FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */,
FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */,
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */,
FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */,

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

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

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

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

@ -20,9 +20,9 @@ class Tournament : ModelObject, Storable {
var endDate: Date?
private(set) var creationDate: Date
var isPrivate: Bool
var groupStageFormat: MatchFormat?
var roundFormat: MatchFormat?
var loserRoundFormat: MatchFormat?
private var groupStageFormat: MatchFormat?
private var roundFormat: MatchFormat?
private var loserRoundFormat: MatchFormat?
var groupStageSortMode: GroupStageOrderingMode
var groupStageCount: Int
var rankSourceDate: Date?
@ -1073,14 +1073,13 @@ class Tournament : ModelObject, Storable {
var femaleUnrankedValue: Int? {
currentMonthData()?.femaleUnrankedValue
}
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
club()?.courts.first(where: { $0.index == courtIndex })?.name
}
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)
}
courtNameIfAvailable(atIndex: courtIndex) ?? 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 {
case 1...10: return 400
case 11...30: return 1000

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

@ -34,12 +34,14 @@ struct MainView: View {
.tabItem(for: .activity)
TournamentOrganizerView()
.tabItem(for: .tournamentOrganizer)
OngoingView()
.tabItem(for: .ongoing)
ToolboxView()
.tabItem(for: .toolbox)
UmpireView()
.tabItem(for: .umpire)
PadelClubView()
.tabItem(for: .padelClub)
// PadelClubView()
// .tabItem(for: .padelClub)
}
.environmentObject(dataStore)
.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 {
NavigationStack {
List {
if let _lastDataSourceDate {
Section {
LabeledContent {
Image(systemName: "checkmark")
} label: {
Text(_lastDataSourceDate.monthYearFormatted)
Text("Classement mensuel utilisé")
}
List {
if let _lastDataSourceDate {
Section {
LabeledContent {
Image(systemName: "checkmark")
} label: {
Text(_lastDataSourceDate.monthYearFormatted)
Text("Classement mensuel utilisé")
}
}
let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed()
ForEach(monthData) { monthData in
Section {
LabeledContent {
if let maleUnrankedValue = monthData.maleUnrankedValue {
Text(maleUnrankedValue.formatted())
}
} label: {
Text("Messieurs")
Text("Rang d'un non classé")
}
let monthData = dataStore.monthData.sorted(by: \.creationDate).reversed()
ForEach(monthData) { monthData in
Section {
LabeledContent {
if let maleUnrankedValue = monthData.maleUnrankedValue {
Text(maleUnrankedValue.formatted())
}
LabeledContent {
if let femaleUnrankedValue = monthData.femaleUnrankedValue {
Text(femaleUnrankedValue.formatted())
}
} label: {
Text("Dames")
Text("Rang d'une non classée")
} label: {
Text("Messieurs")
Text("Rang d'un non classé")
}
LabeledContent {
if let femaleUnrankedValue = monthData.femaleUnrankedValue {
Text(femaleUnrankedValue.formatted())
}
} header: {
Text(monthData.monthKey)
} label: {
Text("Dames")
Text("Rang d'une non classée")
}
} header: {
Text(monthData.monthKey)
}
}
//
// if players.isEmpty {
// 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

@ -10,7 +10,19 @@ import CoreLocation
struct UmpireView: View {
@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 {
NavigationStack {
List {
@ -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 {
// Text("Tenup ID")
// }

@ -38,8 +38,11 @@ struct CourtAvailabilitySettingsView: View {
Text(dateInterval.startDate.localizedDay()).font(.caption)
}
Spacer()
Image(systemName: "arrowshape.forward.fill")
.tint(.master)
VStack {
Image(systemName: "arrowshape.forward.fill")
.tint(.master)
Text("indisponible").foregroundStyle(.red).font(.caption)
}
Spacer()
VStack(alignment: .trailing, spacing: 0) {
Text(dateInterval.endDate.localizedTime()).font(.largeTitle)
@ -70,27 +73,39 @@ struct CourtAvailabilitySettingsView: View {
}
}
} header: {
Text(tournament.courtName(atIndex: key))
HStack {
Text(Court.courtIndexedTitle(atIndex: key))
Spacer()
if let courtName = tournament.courtNameIfAvailable(atIndex: key) {
Text(courtName)
}
}
}
.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 {
ToolbarItem(placement: .topBarTrailing) {
Button {
BarButtonView("Ajouter une indisponibilité", icon: "plus.circle.fill") {
showingPopover = true
} label: {
Image(systemName: "plus.circle.fill")
.resizable()
.scaledToFit()
.frame(minHeight: 28)
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Créneaux")
.navigationTitle("Créneau indisponible")
.popover(isPresented: $showingPopover) {
NavigationStack {
Form {

@ -41,12 +41,12 @@ struct TournamentMatchFormatsSettingsView: View {
Label("Définir les durées moyennes", systemImage: "deskclock")
}
} 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,
tournament.groupStageFormat,
tournament.loserRoundFormat,
.onChange(of: [tournament.loserBracketMatchFormat,
tournament.groupStageMatchFormat,
tournament.matchFormat,
]) {
_save()
_confirmOrSave()

Loading…
Cancel
Save