diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 0b319c2..5425ef6 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -168,6 +168,8 @@ FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; + FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; + FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.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 */; }; @@ -397,6 +399,8 @@ FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = ""; }; FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = ""; }; FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = ""; }; + FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.swift; sourceTree = ""; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = ""; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = ""; }; @@ -778,6 +782,8 @@ FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */, FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */, FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */, + FFB9C8702BBADDE200A0EF4F /* Selectable.swift */, + FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */, ); path = ViewModel; sourceTree = ""; @@ -1153,6 +1159,7 @@ FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, + FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */, FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */, C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */, @@ -1255,6 +1262,7 @@ FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, + FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index b6715da..be36bc8 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -118,4 +118,8 @@ extension GroupStage: Selectable { func selectionLabel() -> String { groupStageTitle() } + + func badgeValue() -> Int? { + nil + } } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 8240cae..5943fb5 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -96,4 +96,8 @@ extension Round: Selectable { func selectionLabel() -> String { roundTitle() } + + func badgeValue() -> Int? { + nil + } } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index bace11c..a6bf4ef 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -159,10 +159,6 @@ class TeamRegistration: ModelObject, Storable { } } - var computedPosition: Int { - groupStagePosition ?? -1 - } - func available() -> Bool { groupStage == nil && bracketPosition == nil } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 22a320c..3c3fd7e 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -564,13 +564,15 @@ class Tournament : ModelObject, Storable { } func buildStructure() { + deleteStructure() + deleteGroupStages() buildGroupStages() buildBracket() } func buildGroupStages() { - groupStages().forEach { groupStage in - try? DataStore.shared.groupStages.delete(instance: groupStage) + guard groupStages().isEmpty else { + return } var _groupStages = [GroupStage]() @@ -591,13 +593,7 @@ class Tournament : ModelObject, Storable { } func buildBracket() { - try? DataStore.shared.rounds.delete(contentOfs: rounds()) - -// if let loserBrackets { -// removeFromLoserBrackets(loserBrackets) -// } - - unsortedTeams().forEach({ $0.bracketPosition = nil }) + guard rounds().isEmpty else { return } let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) let rounds = (0.. Int? { + switch self { + case .activity: + DataStore.shared.tournaments.filter { $0.endDate == nil }.count + case .history: + DataStore.shared.tournaments.filter { $0.endDate != nil }.count + case .tenup: + nil + } + } } diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift new file mode 100644 index 0000000..506be68 --- /dev/null +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -0,0 +1,35 @@ +// +// SeedInterval.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/04/2024. +// + +import Foundation + +struct SeedInterval: Hashable, Comparable { + let first: Int + let last: Int + + static func <(lhs: SeedInterval, rhs: SeedInterval) -> Bool { + return lhs.first < rhs.first + } + + func chunk() -> SeedInterval? { + if last - (last - first) / 2 > first { + return SeedInterval(first: first, last: last - (last - first) / 2) + } else { + return nil + } + } +} + +extension SeedInterval { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + if last - first < 2 { + return "#\(first) / #\(last)" + } else { + return "#\(first) à #\(last)" + } + } +} diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift new file mode 100644 index 0000000..5836812 --- /dev/null +++ b/PadelClub/ViewModel/Selectable.swift @@ -0,0 +1,13 @@ +// +// Selectable.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/04/2024. +// + +import Foundation + +protocol Selectable { + func selectionLabel() -> String + func badgeValue() -> Int? +} diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index b8d2987..9390c99 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -7,11 +7,8 @@ import SwiftUI -protocol Selectable { - func selectionLabel() -> String -} - struct GenericDestinationPickerView: View { + @EnvironmentObject var dataStore: DataStore @Binding var selectedDestination: T? let destinations: [T] let nilDestinationIsValid: Bool @@ -47,6 +44,18 @@ struct GenericDestinationPickerView: View { .opacity(selectedDestination?.id == destination.id ? 1.0 : 0.5) } .buttonStyle(.plain) + .overlay(alignment: .bottomTrailing) { + if let count = destination.badgeValue(), count > 0 { + Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") + .foregroundColor(.secondary) + .imageScale(.medium) + .background ( + Color(.systemBackground) + .clipShape(.circle) + ) + .offset(x: 5, y: 5) + } + } } } .fixedSize() diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 2ba3077..d6fafb2 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -12,6 +12,16 @@ struct GroupStageSettingsView: View { var body: some View { List { + Menu { + //menuAddGroupStage + menuBuildAllGroupStages + menuGenerateGroupStage(.random) + menuGenerateGroupStage(.snake) + //menuGenerateGroupStage(.swiss) + } label: { + LabelOptions() + } + if tournament.missingQualifiedFromGroupStages().isEmpty == false && tournament.qualifiedTeams().count >= tournament.qualifiedFromGroupStage() && tournament.groupStageAdditionalQualified > 0 { NavigationLink { //DrawView(tournament: tournament) @@ -137,6 +147,73 @@ struct GroupStageSettingsView: View { } } + + + var menuBuildAllGroupStages: some View { + Button(role: .destructive) { + tournament.deleteGroupStages() + tournament.buildGroupStages() + } label: { + Label("Refaire les poules", systemImage: "restart") + } + } + + @ViewBuilder + func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View { + Button(role: .destructive) { + tournament.groupStageOrderingMode = mode + tournament.refreshGroupStages() + //save() + } label: { + Label("Poule \(mode.localizedLabel().lowercased())", systemImage: mode.systemImage) + } + } + +// func addGroupStage(_ size: Int64) { +// let groupStage = GroupStage(context: viewContext) +// groupStage.index = tournament.firstIndexToUseForNewGroupStage +// groupStage.size = Int64(size) +// groupStage.matchFormatRawValue = tournament.groupStageMatchFormatRawValue +// print("addGroupStage groupStagesCount", tournament.groupStagesCount) +// print("addGroupStage numberOfGroupStages", tournament.numberOfGroupStages) +// if tournament.groupStagesCount >= tournament.numberOfGroupStages { +// tournament.numberOfGroupStages += 1 +// } +// tournament.addToGroupStages(groupStage) +// save() +// } +// +// var menuAddGroupStage: some View { +// Menu { +// ForEach(-1...1) { index in +// let i = tournament.teamsPerGroupStage + Int64(index) +// Button { +// addGroupStage(i) +// } label: { +// Text("Poule de \(i)") +// } +// .disabled(i < 2) +// } +// } label: { +// Label("Ajouter une poule", systemImage: "server.rack") +// } +// +// } + + +// func save() { +// do { +// tournament.objectWillChange.send() +// try viewContext.save() +// viewContext.refreshAllObjects() +// } catch { +// // Replace this implementation with code to handle the error appropriately. +// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. +// let nsError = error as NSError +// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") +// } +// } + } #Preview { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 9acadc9..f3ef141 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -30,97 +30,5 @@ struct GroupStagesView: View { } .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Menu { -// menuAddGroupStage -// menuBuildAllGroupStages -// menuGenerateGroupStage(.random) -// menuGenerateGroupStage(.snake) -// menuGenerateGroupStage(.swiss) - } label: { - LabelOptions() - } - } - } } -// -// var menuBuildAllGroupStages: some View { -// Button(role: .destructive) { -// tournament.orderedEntries.forEach { entrant in -// if entrant.groupStagePosition > 0 { -// entrant.resetGroupStagePosition() -// } -// } -// tournament.buildGroupStages() -// do { -// try viewContext.save() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// -// } label: { -// Label("Refaire les poules", systemImage: "restart") -// } -// } -// -// @ViewBuilder -// func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View { -// Button(role: .destructive) { -// tournament.stopBroadcastGroupStages() -// tournament.groupStageOrderingMode = mode -// tournament.refreshGroupStages() -// save() -// } label: { -// Label("Poule \(mode.localizedLabel.lowercased())", systemImage: mode.systemImage) -// } -// } -// -// func addGroupStage(_ size: Int64) { -// let groupStage = GroupStage(context: viewContext) -// groupStage.index = tournament.firstIndexToUseForNewGroupStage -// groupStage.size = Int64(size) -// groupStage.matchFormatRawValue = tournament.groupStageMatchFormatRawValue -// print("addGroupStage groupStagesCount", tournament.groupStagesCount) -// print("addGroupStage numberOfGroupStages", tournament.numberOfGroupStages) -// if tournament.groupStagesCount >= tournament.numberOfGroupStages { -// tournament.numberOfGroupStages += 1 -// } -// tournament.addToGroupStages(groupStage) -// save() -// } -// -// var menuAddGroupStage: some View { -// Menu { -// ForEach(-1...1) { index in -// let i = tournament.teamsPerGroupStage + Int64(index) -// Button { -// addGroupStage(i) -// } label: { -// Text("Poule de \(i)") -// } -// .disabled(i < 2) -// } -// } label: { -// Label("Ajouter une poule", systemImage: "server.rack") -// } -// -// } -// -// -// func save() { -// do { -// tournament.objectWillChange.send() -// try viewContext.save() -// viewContext.refreshAllObjects() -// } catch { -// // Replace this implementation with code to handle the error appropriately. -// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. -// let nsError = error as NSError -// fatalError("Unresolved error \(nsError), \(nsError.userInfo)") -// } -// } } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 6391370..8309546 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -39,8 +39,9 @@ struct MatchSetupView: View { try? dataStore.teamRegistrations.addOrUpdate(instance: team) }) if let tournament = match.currentTournament() { + let availableSeedGroups = tournament.availableSeedGroups() Menu { - ForEach(tournament.availableSeedGroups(), id: \.self) { seedGroup in + ForEach(availableSeedGroups, id: \.self) { seedGroup in Button { if let randomTeam = tournament.randomSeed(fromSeedGroup: seedGroup) { randomTeam.setSeedPosition(inSpot: match, upperBranch: teamPosition, opposingSeeding: false) @@ -54,6 +55,7 @@ struct MatchSetupView: View { } label: { Text("Tirage").tag(nil as SeedInterval?) } + .disabled(availableSeedGroups.isEmpty) } } .fixedSize(horizontal: false, vertical: true) diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 7f9362f..edef755 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -102,30 +102,3 @@ struct RoundSettingsView: View { .environment(Tournament.mock()) .environmentObject(DataStore.shared) } - -struct SeedInterval: Hashable, Comparable { - let first: Int - let last: Int - - static func <(lhs: SeedInterval, rhs: SeedInterval) -> Bool { - return lhs.first < rhs.first - } - - func chunk() -> SeedInterval? { - if last - (last - first) / 2 > first { - return SeedInterval(first: first, last: last - (last - first) / 2) - } else { - return nil - } - } -} - -extension SeedInterval { - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - if last - first < 2 { - return "#\(first) / #\(last)" - } else { - return "#\(first) à #\(last)" - } - } -} diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index 7314258..ec5b727 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -240,7 +240,7 @@ struct TableStructureView: View { let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding }) if (rebuildEverything == false && requirements.contains(.all)) || rebuildEverything { - tournament.resetStructure() + tournament.deleteStructure() } tournament.teamCount = teamCount