|
|
|
@ -147,6 +147,13 @@ struct PlanningSettingsView: View { |
|
|
|
let allGroupStages = tournament.allGroupStages() |
|
|
|
let allGroupStages = tournament.allGroupStages() |
|
|
|
let allRounds = tournament.allRounds() |
|
|
|
let allRounds = tournament.allRounds() |
|
|
|
let matchesWithDate = allMatches.filter({ $0.startDate != nil }) |
|
|
|
let matchesWithDate = allMatches.filter({ $0.startDate != nil }) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let groupMatchesByDay = _groupMatchesByDay(matches: matchesWithDate) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let countedSet = _matchCountPerDay(matchesByDay: groupMatchesByDay, tournament: tournament) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_formatPerDayView(matchCountPerDay: countedSet) |
|
|
|
|
|
|
|
|
|
|
|
let groupStagesWithDate = allGroupStages.filter({ $0.startDate != nil }) |
|
|
|
let groupStagesWithDate = allGroupStages.filter({ $0.startDate != nil }) |
|
|
|
let roundsWithDate = allRounds.filter({ $0.startDate != nil }) |
|
|
|
let roundsWithDate = allRounds.filter({ $0.startDate != nil }) |
|
|
|
if matchesWithDate.isEmpty == false || groupStagesWithDate.isEmpty == false || roundsWithDate.isEmpty == false { |
|
|
|
if matchesWithDate.isEmpty == false || groupStagesWithDate.isEmpty == false || roundsWithDate.isEmpty == false { |
|
|
|
@ -391,6 +398,135 @@ struct PlanningSettingsView: View { |
|
|
|
Logger.error(error) |
|
|
|
Logger.error(error) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func _groupMatchesByDay(matches: [Match]) -> [Date: [Match]] { |
|
|
|
|
|
|
|
var matchesByDay = [Date: [Match]]() |
|
|
|
|
|
|
|
let calendar = Calendar.current |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for match in matches { |
|
|
|
|
|
|
|
// Extract day/month/year and create a date with only these components |
|
|
|
|
|
|
|
let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting) |
|
|
|
|
|
|
|
let strippedDate = calendar.date(from: components)! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Group matches by the strippedDate (only day/month/year) |
|
|
|
|
|
|
|
if matchesByDay[strippedDate] == nil { |
|
|
|
|
|
|
|
matchesByDay[strippedDate] = [] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let shouldIncludeMatch: Bool |
|
|
|
|
|
|
|
switch match.matchType { |
|
|
|
|
|
|
|
case .groupStage: |
|
|
|
|
|
|
|
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.groupStage != nil }.compactMap { $0.groupStage }.contains(match.groupStage!) |
|
|
|
|
|
|
|
case .bracket: |
|
|
|
|
|
|
|
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.round != nil }.compactMap { $0.round }.contains(match.round!) |
|
|
|
|
|
|
|
case .loserBracket: |
|
|
|
|
|
|
|
shouldIncludeMatch = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if shouldIncludeMatch { |
|
|
|
|
|
|
|
matchesByDay[strippedDate]!.append(match) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return matchesByDay |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func _matchCountPerDay(matchesByDay: [Date: [Match]], tournament: Tournament) -> [Date: NSCountedSet] { |
|
|
|
|
|
|
|
let days = matchesByDay.keys |
|
|
|
|
|
|
|
var matchCountPerDay = [Date: NSCountedSet]() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for day in days { |
|
|
|
|
|
|
|
if let matches = matchesByDay[day] { |
|
|
|
|
|
|
|
var groupStageCount = 0 |
|
|
|
|
|
|
|
let countedSet = NSCountedSet() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for match in matches { |
|
|
|
|
|
|
|
switch match.matchType { |
|
|
|
|
|
|
|
case .groupStage: |
|
|
|
|
|
|
|
if let groupStage = match.groupStageObject { |
|
|
|
|
|
|
|
if groupStageCount < groupStage.size - 1 { |
|
|
|
|
|
|
|
groupStageCount = groupStage.size - 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
case .bracket: |
|
|
|
|
|
|
|
countedSet.add(match.matchFormat) |
|
|
|
|
|
|
|
case .loserBracket: |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if groupStageCount > 0 { |
|
|
|
|
|
|
|
for _ in 0..<groupStageCount { |
|
|
|
|
|
|
|
countedSet.add(tournament.groupStageMatchFormat) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let loserRounds = matches.filter({ $0.round != nil }).filter({ $0.roundObject?.parent == nil }).sorted(by: \.computedStartDateForSorting).last?.roundObject?.loserRounds() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let ids = matches.map { $0.id } |
|
|
|
|
|
|
|
for loserRound in loserRounds { |
|
|
|
|
|
|
|
if let first = loserRound.playedMatches().first { |
|
|
|
|
|
|
|
if ids.contains(first.id) { |
|
|
|
|
|
|
|
countedSet.add(first.matchFormat) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
matchCountPerDay[day] = countedSet |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return matchCountPerDay |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func _formatPerDayView(matchCountPerDay: [Date: NSCountedSet]) -> some View { |
|
|
|
|
|
|
|
ForEach(Array(matchCountPerDay.keys).sorted(), id: \.self) { date in |
|
|
|
|
|
|
|
if let countedSet = matchCountPerDay[date] { |
|
|
|
|
|
|
|
Section { |
|
|
|
|
|
|
|
let totalMatches = countedSet.totalCount() |
|
|
|
|
|
|
|
ForEach(Array(countedSet).compactMap { $0 as? MatchFormat }, id: \.self) { matchFormat in |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let count = countedSet.count(for: matchFormat) |
|
|
|
|
|
|
|
let totalForThisFormat = matchFormat.maximumMatchPerDay(for: totalMatches) |
|
|
|
|
|
|
|
let error = count > totalForThisFormat |
|
|
|
|
|
|
|
// Presenting LabeledContent for each match format and its count |
|
|
|
|
|
|
|
LabeledContent { |
|
|
|
|
|
|
|
Image(systemName: error ? "exclamationmark.triangle" : "checkmark") |
|
|
|
|
|
|
|
.font(.title3) |
|
|
|
|
|
|
|
.foregroundStyle(error ? .red : .green) |
|
|
|
|
|
|
|
} label: { |
|
|
|
|
|
|
|
let label = "\(count) match\(count.pluralSuffix) en \(matchFormat.format)" |
|
|
|
|
|
|
|
let subtitle = "pas plus de " + totalForThisFormat.formatted() + " match\(totalForThisFormat.pluralSuffix) pour ce jour" |
|
|
|
|
|
|
|
Text(label) |
|
|
|
|
|
|
|
Text(subtitle) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} header: { |
|
|
|
|
|
|
|
Text(date.formatted(.dateTime.weekday(.abbreviated).day(.twoDigits).month(.abbreviated))) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Helper function to format date to string (you can customize the format) |
|
|
|
|
|
|
|
private func _formattedDate(_ date: Date) -> String { |
|
|
|
|
|
|
|
let formatter = DateFormatter() |
|
|
|
|
|
|
|
formatter.dateStyle = .medium // Set the date style (e.g., Oct 12, 2024) |
|
|
|
|
|
|
|
return formatter.string(from: date) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Extension to compute the total count in an NSCountedSet |
|
|
|
|
|
|
|
extension NSCountedSet { |
|
|
|
|
|
|
|
func totalCount() -> Int { |
|
|
|
|
|
|
|
var total = 0 |
|
|
|
|
|
|
|
for element in self { |
|
|
|
|
|
|
|
total += self.count(for: element) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return total |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//#Preview { |
|
|
|
//#Preview { |
|
|
|
|