fix planning views and match scheduler for groupstages

sync2
Raz 1 year ago
parent f691681e94
commit 883a46baea
  1. 8
      PadelClub.xcodeproj/project.pbxproj
  2. 6
      PadelClub/Data/GroupStage.swift
  3. 5
      PadelClub/Data/Match.swift
  4. 11
      PadelClub/Data/MatchScheduler.swift
  5. 10
      PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift
  6. 15
      PadelClub/Views/Match/Components/MatchTeamDetailView.swift
  7. 2
      PadelClub/Views/Match/MatchSummaryView.swift
  8. 1
      PadelClub/Views/Planning/PlanningByCourtView.swift
  9. 46
      PadelClub/Views/Planning/PlanningView.swift

@ -3134,7 +3134,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 6;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3178,7 +3178,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 6;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -3293,7 +3293,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3335,7 +3335,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -50,7 +50,7 @@ final class GroupStage: ModelObject, Storable {
// MARK: - Computed dependencies // MARK: - Computed dependencies
func _matches() -> [Match] { func _matches() -> [Match] {
return self.tournamentStore.matches.filter { $0.groupStage == self.id } return self.tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index)
// Store.main.filter { $0.groupStage == self.id } // Store.main.filter { $0.groupStage == self.id }
} }
@ -276,6 +276,10 @@ final class GroupStage: ModelObject, Storable {
} }
} }
func indexOf(_ matchIndex: Int) -> Int {
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex
}
private func _matchUp(for matchIndex: Int) -> [Int] { private func _matchUp(for matchIndex: Int) -> [Int] {
Array((0..<size).combinations(ofCount: 2))[safe: matchIndex] ?? [] Array((0..<size).combinations(ofCount: 2))[safe: matchIndex] ?? []
} }

@ -470,8 +470,11 @@ defer {
} }
var computedOrder: Int { var computedOrder: Int {
if let groupStageObject {
return (groupStageObject.index + 1) * 100 + groupStageObject.indexOf(index)
}
guard let roundObject else { return index } guard let roundObject else { return index }
return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound() return roundObject.isLoserBracket() ? (roundObject.index + 1) * 1000 + indexInRound() : (roundObject.index + 1) * 10000 + indexInRound()
} }
func previousMatches() -> [Match] { func previousMatches() -> [Match] {

@ -199,11 +199,11 @@ final class MatchScheduler : ModelObject, Storable {
while slots.count < flattenedMatches.count { while slots.count < flattenedMatches.count {
teamsPerRotation[rotationIndex] = [] teamsPerRotation[rotationIndex] = []
freeCourtPerRotation[rotationIndex] = [] freeCourtPerRotation[rotationIndex] = []
(0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in let previousRotationBracketIndexes = slots.filter { $0.rotationIndex == rotationIndex - 1 }.map { ($0.groupIndex, 1) }
//print(mt.map { ($0.bracket!.index.intValue, counts[$0.bracket!.index.intValue]) })
let previousRotationBracketIndexes = slots.map { ($0.groupIndex, 1) }
let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +) let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +)
var rotationMatches = Array(availableMatchs) var rotationMatches = Array(availableMatchs.filter({ match in
teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true
}).prefix(numberOfCourtsAvailablePerRotation))
if rotationIndex > 0 { if rotationIndex > 0 {
rotationMatches = rotationMatches.sorted(by: { rotationMatches = rotationMatches.sorted(by: {
@ -215,6 +215,8 @@ final class MatchScheduler : ModelObject, Storable {
}) })
} }
(0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in
//print(mt.map { ($0.bracket!.index.intValue, counts[$0.bracket!.index.intValue]) })
if let first = rotationMatches.first(where: { match in if let first = rotationMatches.first(where: { match in
let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration) let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60 let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60
@ -228,6 +230,7 @@ final class MatchScheduler : ModelObject, Storable {
} }
}) { }) {
let timeMatch = GroupStageTimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, groupIndex: first.groupStageObject!.index) let timeMatch = GroupStageTimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, groupIndex: first.groupStageObject!.index)
print(first.matchTitle())
slots.append(timeMatch) slots.append(timeMatch)
teamsPerRotation[rotationIndex]!.append(contentsOf: first.teamIds()) teamsPerRotation[rotationIndex]!.append(contentsOf: first.teamIds())
rotationMatches.removeAll(where: { $0.id == first.id }) rotationMatches.removeAll(where: { $0.id == first.id })

@ -37,6 +37,14 @@ struct GroupStageTeamView: View {
} }
} }
private func _editingOptions() -> [EditablePlayerView.PlayerEditingOption] {
if tournament.isFree() {
return [.licenceId, .name, .presence]
} else {
return [.licenceId, .name, .payment]
}
}
var body: some View { var body: some View {
List { List {
Section { Section {
@ -44,7 +52,7 @@ struct GroupStageTeamView: View {
Text(name).foregroundStyle(.secondary) Text(name).foregroundStyle(.secondary)
} }
ForEach(team.players()) { player in ForEach(team.players()) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: _editingOptions())
} }
} }

@ -32,13 +32,26 @@ struct MatchTeamDetailView: View {
private func _teamDetailView(_ team: TeamRegistration, inTournament tournament: Tournament?) -> some View { private func _teamDetailView(_ team: TeamRegistration, inTournament tournament: Tournament?) -> some View {
Section { Section {
ForEach(team.players()) { player in ForEach(team.players()) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: _editingOptions())
} }
} header: { } header: {
TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team)) TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team))
} }
} }
private func _isFree() -> Bool {
let tournament = match.currentTournament()
return tournament?.isFree() == true
}
private func _editingOptions() -> [EditablePlayerView.PlayerEditingOption] {
if _isFree() {
return [.licenceId, .name, .presence]
} else {
return [.licenceId, .name, .payment]
}
}
} }
//#Preview { //#Preview {

@ -57,7 +57,7 @@ struct MatchSummaryView: View {
} }
} }
Spacer() Spacer()
if let courtName, matchViewStyle != .feedStyle { if let courtName {
Spacer() Spacer()
Text(courtName) Text(courtName)
.foregroundStyle(.gray) .foregroundStyle(.gray)

@ -40,6 +40,7 @@ struct PlanningByCourtView: View {
var body: some View { var body: some View {
List { List {
_byCourtView() _byCourtView()
.id(selectedCourt)
} }
.overlay { .overlay {
if matches.allSatisfy({ $0.startDate == nil }) { if matches.allSatisfy({ $0.startDate == nil }) {

@ -16,6 +16,23 @@ struct PlanningView: View {
@State private var timeSlots: [Date:[Match]] @State private var timeSlots: [Date:[Match]]
@State private var days: [Date] @State private var days: [Date]
@State private var keys: [Date] @State private var keys: [Date]
@State private var filterOption: PlanningFilterOption = .byDefault
enum PlanningFilterOption: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
case byDefault
case byCourt
func localizedPlanningLabel() -> String {
switch self {
case .byCourt:
return "Par terrain"
case .byDefault:
return "Par défaut"
}
}
}
init(matches: [Match], selectedScheduleDestination: Binding<ScheduleDestination?>) { init(matches: [Match], selectedScheduleDestination: Binding<ScheduleDestination?>) {
self.matches = matches self.matches = matches
@ -30,6 +47,24 @@ struct PlanningView: View {
List { List {
_bySlotView() _bySlotView()
} }
.toolbar(content: {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Picker(selection: $filterOption) {
ForEach(PlanningFilterOption.allCases) {
Text($0.localizedPlanningLabel()).tag($0)
}
} label: {
Text("Option de filtrage")
}
.labelsHidden()
.pickerStyle(.inline)
} label: {
Label("Filtrer", systemImage: "line.3.horizontal.decrease.circle")
.symbolVariant(filterOption == .byCourt ? .fill : .none)
}
}
})
.overlay { .overlay {
if matches.allSatisfy({ $0.startDate == nil }) { if matches.allSatisfy({ $0.startDate == nil }) {
ContentUnavailableView { ContentUnavailableView {
@ -53,7 +88,7 @@ struct PlanningView: View {
ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in
if let _matches = timeSlots[key] { if let _matches = timeSlots[key] {
DisclosureGroup { DisclosureGroup {
ForEach(_matches) { match in ForEach(_matches.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting)) { match in
NavigationLink { NavigationLink {
MatchDetailView(match: match, matchViewStyle: .sectionedStandardStyle) MatchDetailView(match: match, matchViewStyle: .sectionedStandardStyle)
} label: { } label: {
@ -98,7 +133,14 @@ struct PlanningView: View {
Text(self._formattedMatchCount(matches.count)) Text(self._formattedMatchCount(matches.count))
} label: { } label: {
Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold)
Text(Set(matches.compactMap { $0.roundTitle() }).joined(separator: ", ")) let names = matches.sorted(by: \.computedOrder)
.compactMap({ $0.roundTitle() })
.reduce(into: [String]()) { uniqueNames, name in
if !uniqueNames.contains(name) {
uniqueNames.append(name)
}
}
Text(names.joined(separator: ", "))
} }
} }

Loading…
Cancel
Save