commit
f3c2d1b047
@ -0,0 +1,144 @@ |
||||
// |
||||
// PlanningByCourtView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by razmig on 24/08/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PlanningByCourtView: View { |
||||
@EnvironmentObject var dataStore: DataStore |
||||
@Environment(Tournament.self) var tournament: Tournament |
||||
|
||||
let matches: [Match] |
||||
@Binding var selectedScheduleDestination: ScheduleDestination? |
||||
@State private var timeSlots: [Date:[Match]] |
||||
@State private var days: [Date] |
||||
@State private var keys: [Date] |
||||
@State private var courts: [Int] |
||||
@State private var viewByCourt: Bool = false |
||||
@State private var courtSlots: [Int:[Match]] |
||||
@State private var selectedDay: Date |
||||
@State private var selectedCourt: Int = 0 |
||||
|
||||
|
||||
init(matches: [Match], selectedScheduleDestination: Binding<ScheduleDestination?>, startDate: Date) { |
||||
self.matches = matches |
||||
_selectedScheduleDestination = selectedScheduleDestination |
||||
let timeSlots = Dictionary(grouping: matches) { $0.startDate ?? .distantFuture } |
||||
let courtSlots = Dictionary(grouping: matches) { $0.courtIndex ?? Int.max} |
||||
_timeSlots = State(wrappedValue: timeSlots) |
||||
_courtSlots = State(wrappedValue: courtSlots) |
||||
_days = State(wrappedValue: Set(timeSlots.keys.map { $0.startOfDay }).sorted()) |
||||
_keys = State(wrappedValue: timeSlots.keys.sorted()) |
||||
_courts = State(wrappedValue: courtSlots.keys.sorted()) |
||||
|
||||
_selectedDay = State(wrappedValue: startDate) |
||||
} |
||||
|
||||
var body: some View { |
||||
List { |
||||
_byCourtView() |
||||
} |
||||
.overlay { |
||||
if matches.allSatisfy({ $0.startDate == nil }) { |
||||
ContentUnavailableView { |
||||
Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") |
||||
} description: { |
||||
Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") |
||||
} actions: { |
||||
RowButtonView("Horaire intelligent") { |
||||
selectedScheduleDestination = nil |
||||
} |
||||
.padding(.horizontal) |
||||
} |
||||
} |
||||
} |
||||
.navigationTitle(Text(selectedDay.formatted(.dateTime.day().weekday().month()))) |
||||
.toolbar { |
||||
if days.count > 1 { |
||||
ToolbarTitleMenu { |
||||
Picker(selection: $selectedDay) { |
||||
ForEach(days, id: \.self) { day in |
||||
Text(day.formatted(.dateTime.day().weekday().month())).tag(day as Date?) |
||||
} |
||||
} label: { |
||||
Text("Jour") |
||||
} |
||||
.pickerStyle(.automatic) |
||||
} |
||||
} |
||||
ToolbarItemGroup(placement: .topBarTrailing) { |
||||
if courts.count > 1 { |
||||
Picker(selection: $selectedCourt) { |
||||
ForEach(courts, id: \.self) { courtIndex in |
||||
if courtIndex == Int.max { |
||||
Image(systemName: "rectangle.slash").tag(Int.max) |
||||
} else { |
||||
Text(tournament.courtName(atIndex: courtIndex)).tag(courtIndex) |
||||
} |
||||
} |
||||
} label: { |
||||
Text("Terrain") |
||||
} |
||||
.pickerStyle(.automatic) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ViewBuilder |
||||
func _byCourtView() -> some View { |
||||
if let _matches = courtSlots[selectedCourt]?.filter({ $0.startDate?.dayInt == selectedDay.dayInt }) { |
||||
let _sortedMatches = _matches.sorted(by: \.computedStartDateForSorting) |
||||
ForEach(_sortedMatches.indices, id: \.self) { index in |
||||
let match = _sortedMatches[index] |
||||
Section { |
||||
MatchRowView(match: match, matchViewStyle: .feedStyle) |
||||
} header: { |
||||
if let startDate = match.startDate { |
||||
if index > 0 { |
||||
if match.confirmed { |
||||
Text("Pas avant \(startDate.formatted(date: .omitted, time: .shortened))") |
||||
} else { |
||||
Text("Suivi de") |
||||
} |
||||
} else { |
||||
Text("Démarrage à \(startDate.formatted(date: .omitted, time: .shortened))") |
||||
} |
||||
} else { |
||||
Text("Aucun horaire") |
||||
} |
||||
} |
||||
.headerProminence(.increased) |
||||
} |
||||
} else if courtSlots.isEmpty == false { |
||||
ContentUnavailableView { |
||||
Label("Aucun match plannifié", systemImage: "clock.badge.questionmark") |
||||
} description: { |
||||
Text("Aucun match n'a été plannifié sur ce terrain et au jour sélectionné") |
||||
} actions: { |
||||
} |
||||
} |
||||
} |
||||
|
||||
private func _matchesCount(inDayInt dayInt: Int) -> Int { |
||||
timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count |
||||
} |
||||
|
||||
private func _timeSlotView(key: Date, matches: [Match]) -> some View { |
||||
LabeledContent { |
||||
Text(self._formattedMatchCount(matches.count)) |
||||
} label: { |
||||
Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) |
||||
Text(Set(matches.compactMap { $0.roundTitle() }).joined(separator: ", ")) |
||||
} |
||||
} |
||||
|
||||
fileprivate func _formattedMatchCount(_ count: Int) -> String { |
||||
return "\(count.formatted()) match\(count.pluralSuffix)" |
||||
} |
||||
|
||||
|
||||
} |
||||
Loading…
Reference in new issue