add groupstage redo options

multistore
Razmig Sarkissian 2 years ago
parent c5e1f4b356
commit 55b69bc9b4
  1. 8
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Data/GroupStage.swift
  3. 4
      PadelClub/Data/Round.swift
  4. 4
      PadelClub/Data/TeamRegistration.swift
  5. 32
      PadelClub/Data/Tournament.swift
  6. 11
      PadelClub/ViewModel/AgendaDestination.swift
  7. 35
      PadelClub/ViewModel/SeedInterval.swift
  8. 13
      PadelClub/ViewModel/Selectable.swift
  9. 17
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  10. 77
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  11. 92
      PadelClub/Views/GroupStage/GroupStagesView.swift
  12. 4
      PadelClub/Views/Match/MatchSetupView.swift
  13. 27
      PadelClub/Views/Round/RoundSettingsView.swift
  14. 2
      PadelClub/Views/Tournament/Screen/TableStructureView.swift

@ -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 = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = "<group>"; };
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedInterval.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>"; };
@ -778,6 +782,8 @@
FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */,
FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */,
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */,
FFB9C8702BBADDE200A0EF4F /* Selectable.swift */,
FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -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 */,

@ -118,4 +118,8 @@ extension GroupStage: Selectable {
func selectionLabel() -> String {
groupStageTitle()
}
func badgeValue() -> Int? {
nil
}
}

@ -96,4 +96,8 @@ extension Round: Selectable {
func selectionLabel() -> String {
roundTitle()
}
func badgeValue() -> Int? {
nil
}
}

@ -159,10 +159,6 @@ class TeamRegistration: ModelObject, Storable {
}
}
var computedPosition: Int {
groupStagePosition ?? -1
}
func available() -> Bool {
groupStage == nil && bracketPosition == nil
}

@ -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..<roundCount).map { //index 0 is the final
@ -620,12 +616,19 @@ class Tournament : ModelObject, Storable {
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
}
func resetStructure() {
func deleteStructure() {
try? DataStore.shared.rounds.delete(contentOfs: rounds())
unsortedTeams().forEach({ $0.bracketPosition = nil })
try? DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
}
func resetGroupStages() {
func deleteGroupStages() {
try? DataStore.shared.groupStages.delete(contentOfs: groupStages())
unsortedTeams().forEach({
$0.groupStage = nil
$0.groupStagePosition = nil
})
try? DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
}
func refreshGroupStages() {
@ -651,9 +654,10 @@ class Tournament : ModelObject, Storable {
let numberOfBracketsAsInt = groupStages.count
// let teamsPerBracket = teamsPerBracket
if groupStageCount != numberOfBracketsAsInt {
deleteGroupStages()
buildGroupStages()
return
}
let max = groupStages.map { $0.size }.reduce(0,+)
var chunks = selectedSortedTeams().suffix(max).chunked(into: numberOfBracketsAsInt)
for (index, _) in chunks.enumerated() {

@ -44,4 +44,15 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable {
return "tennisball"
}
}
func badgeValue() -> 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
}
}
}

@ -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)"
}
}
}

@ -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?
}

@ -7,11 +7,8 @@
import SwiftUI
protocol Selectable {
func selectionLabel() -> String
}
struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
@EnvironmentObject var dataStore: DataStore
@Binding var selectedDestination: T?
let destinations: [T]
let nilDestinationIsValid: Bool
@ -47,6 +44,18 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: 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()

@ -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 {

@ -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)")
// }
// }
}

@ -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)

@ -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)"
}
}
}

@ -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

Loading…
Cancel
Save