parent
a486119114
commit
a5d1e26ef6
@ -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