|
|
|
|
@ -110,7 +110,7 @@ struct PlanningView: View { |
|
|
|
|
|
|
|
|
|
if _confirmationMode() { |
|
|
|
|
ToolbarItem(placement: .topBarLeading) { |
|
|
|
|
Button("Annuler") { |
|
|
|
|
Button(enableMove ? "Annuler" : "Terminer") { |
|
|
|
|
enableMove = false |
|
|
|
|
enableEditionBinding.wrappedValue = false |
|
|
|
|
} |
|
|
|
|
@ -130,82 +130,86 @@ struct PlanningView: View { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if notSlots == false { |
|
|
|
|
ToolbarItemGroup(placement: .bottomBar) { |
|
|
|
|
HStack { |
|
|
|
|
ToolbarItemGroup(placement: .topBarTrailing) { |
|
|
|
|
Menu { |
|
|
|
|
if notSlots == false { |
|
|
|
|
CourtOptionsView(timeSlots: timeSlots, underlined: false) |
|
|
|
|
Spacer() |
|
|
|
|
Toggle(isOn: $enableMove) { |
|
|
|
|
Label { |
|
|
|
|
Text("Déplacer") |
|
|
|
|
Text("Déplacer un créneau") |
|
|
|
|
} icon: { |
|
|
|
|
Image(systemName: "rectangle.2.swap") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.popoverTip(timeSlotMoveOptionTip) |
|
|
|
|
.disabled(_confirmationMode()) |
|
|
|
|
Spacer() |
|
|
|
|
Toggle(isOn: enableEditionBinding) { |
|
|
|
|
Text("Modifier") |
|
|
|
|
Text("Modifier un horaire") |
|
|
|
|
} |
|
|
|
|
.disabled(_confirmationMode()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ToolbarItemGroup(placement: .topBarTrailing) { |
|
|
|
|
Menu { |
|
|
|
|
Section { |
|
|
|
|
Picker(selection: $showFinishedMatches) { |
|
|
|
|
Text("Afficher tous les matchs").tag(true) |
|
|
|
|
Text("Masquer les matchs terminés").tag(false) |
|
|
|
|
} label: { |
|
|
|
|
|
|
|
|
|
Divider() |
|
|
|
|
|
|
|
|
|
Menu { |
|
|
|
|
Section { |
|
|
|
|
Picker(selection: $showFinishedMatches) { |
|
|
|
|
Text("Afficher tous les matchs").tag(true) |
|
|
|
|
Text("Masquer les matchs terminés").tag(false) |
|
|
|
|
} label: { |
|
|
|
|
Text("Option de filtrage") |
|
|
|
|
} |
|
|
|
|
.labelsHidden() |
|
|
|
|
.pickerStyle(.inline) |
|
|
|
|
} header: { |
|
|
|
|
Text("Option de filtrage") |
|
|
|
|
} |
|
|
|
|
.labelsHidden() |
|
|
|
|
.pickerStyle(.inline) |
|
|
|
|
} header: { |
|
|
|
|
Text("Option de filtrage") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Divider() |
|
|
|
|
Divider() |
|
|
|
|
|
|
|
|
|
Section { |
|
|
|
|
Picker(selection: $filterOption) { |
|
|
|
|
ForEach(PlanningFilterOption.allCases) { |
|
|
|
|
Text($0.localizedPlanningLabel()).tag($0) |
|
|
|
|
Section { |
|
|
|
|
Picker(selection: $filterOption) { |
|
|
|
|
ForEach(PlanningFilterOption.allCases) { |
|
|
|
|
Text($0.localizedPlanningLabel()).tag($0) |
|
|
|
|
} |
|
|
|
|
} label: { |
|
|
|
|
Text("Option de triage") |
|
|
|
|
} |
|
|
|
|
} label: { |
|
|
|
|
.labelsHidden() |
|
|
|
|
.pickerStyle(.inline) |
|
|
|
|
} header: { |
|
|
|
|
Text("Option de triage") |
|
|
|
|
} |
|
|
|
|
.labelsHidden() |
|
|
|
|
.pickerStyle(.inline) |
|
|
|
|
} header: { |
|
|
|
|
Text("Option de triage") |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} label: { |
|
|
|
|
Label("Trier", systemImage: "line.3.horizontal.decrease.circle") |
|
|
|
|
.symbolVariant( |
|
|
|
|
filterOption == .byCourt || showFinishedMatches ? .fill : .none) |
|
|
|
|
} |
|
|
|
|
} label: { |
|
|
|
|
Label("Trier", systemImage: "line.3.horizontal.decrease.circle") |
|
|
|
|
.symbolVariant( |
|
|
|
|
filterOption == .byCourt || showFinishedMatches ? .fill : .none) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") { |
|
|
|
|
let now = Date() |
|
|
|
|
matches.forEach { |
|
|
|
|
if let startDate = $0.startDate, startDate > now { |
|
|
|
|
$0.plannedStartDate = $0.startDate |
|
|
|
|
Divider() |
|
|
|
|
|
|
|
|
|
Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") { |
|
|
|
|
let now = Date() |
|
|
|
|
matches.forEach { |
|
|
|
|
if let startDate = $0.startDate, startDate > now { |
|
|
|
|
$0.plannedStartDate = $0.startDate |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let groupByTournaments = matches.grouped { match in |
|
|
|
|
match.currentTournament() |
|
|
|
|
} |
|
|
|
|
groupByTournaments.forEach { tournament, matches in |
|
|
|
|
tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let groupByTournaments = matches.grouped { match in |
|
|
|
|
match.currentTournament() |
|
|
|
|
} |
|
|
|
|
groupByTournaments.forEach { tournament, matches in |
|
|
|
|
tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
|
|
|
} |
|
|
|
|
.popoverTip(updatePlannedDatesTip) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} label: { |
|
|
|
|
LabelOptions() |
|
|
|
|
} |
|
|
|
|
.popoverTip(updatePlannedDatesTip) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
@ -235,8 +239,8 @@ struct PlanningView: View { |
|
|
|
|
@Environment(\.editMode) private var editMode |
|
|
|
|
@State private var selectedIds = Set<String>() |
|
|
|
|
@State private var showDateUpdateView: Bool = false |
|
|
|
|
@State private var dateToUpdate: Date = Date() |
|
|
|
|
|
|
|
|
|
@State private var matchesToUpdate: [Match] = [] |
|
|
|
|
|
|
|
|
|
let days: [Date] |
|
|
|
|
let keys: [Date] |
|
|
|
|
let timeSlots: [Date: [Match]] |
|
|
|
|
@ -258,28 +262,34 @@ struct PlanningView: View { |
|
|
|
|
day: day, |
|
|
|
|
keys: keys.filter({ $0.dayInt == day.dayInt }), |
|
|
|
|
timeSlots: timeSlots, |
|
|
|
|
selectedDay: selectedDay |
|
|
|
|
selectedDay: selectedDay, |
|
|
|
|
selectedIds: $selectedIds, |
|
|
|
|
matchesForUpdateSheet: $matchesToUpdate |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.toolbar(content: { |
|
|
|
|
if editMode?.wrappedValue == .active { |
|
|
|
|
ToolbarItem(placement: .bottomBar) { |
|
|
|
|
ToolbarItem(placement: .topBarTrailing) { |
|
|
|
|
Button { |
|
|
|
|
showDateUpdateView = true |
|
|
|
|
matchesToUpdate = matches.filter({ selectedIds.contains($0.stringId) }) |
|
|
|
|
} label: { |
|
|
|
|
Text("Modifier la date des matchs sélectionnés") |
|
|
|
|
Text("Modifier") |
|
|
|
|
} |
|
|
|
|
.buttonStyle(.borderless) |
|
|
|
|
.disabled(selectedIds.isEmpty) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.onChange(of: matchesToUpdate, { oldValue, newValue in |
|
|
|
|
showDateUpdateView = matchesToUpdate.count > 0 |
|
|
|
|
}) |
|
|
|
|
.sheet(isPresented: $showDateUpdateView, onDismiss: { |
|
|
|
|
selectedIds.removeAll() |
|
|
|
|
matchesToUpdate = [] |
|
|
|
|
}) { |
|
|
|
|
let selectedMatches = matches.filter({ selectedIds.contains($0.stringId) }) |
|
|
|
|
DateUpdateView(selectedMatches: selectedMatches) |
|
|
|
|
DateUpdateView(selectedMatches: matchesToUpdate) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -290,6 +300,7 @@ struct PlanningView: View { |
|
|
|
|
let selectedMatches: [Match] |
|
|
|
|
let selectedFormats: [MatchFormat] |
|
|
|
|
@State private var dateToUpdate: Date |
|
|
|
|
@State private var updateStep: Int = 0 |
|
|
|
|
|
|
|
|
|
init(selectedMatches: [Match]) { |
|
|
|
|
self.selectedMatches = selectedMatches |
|
|
|
|
@ -306,13 +317,37 @@ struct PlanningView: View { |
|
|
|
|
DatePicker(selection: $dateToUpdate) { |
|
|
|
|
Text(dateToUpdate.formatted(.dateTime.weekday(.wide))).font(.headline) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
RowButtonView("Définir le nouvel horaire", role: .destructive) { |
|
|
|
|
_setDate() |
|
|
|
|
} |
|
|
|
|
} footer: { |
|
|
|
|
Text("Choisir un nouvel horaire pour tous les matchs sélectionnés") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Section { |
|
|
|
|
DateAdjusterView(date: $dateToUpdate) |
|
|
|
|
DateAdjusterView(date: $dateToUpdate, time: 10) |
|
|
|
|
LabeledContent { |
|
|
|
|
StepperView(title: "minutes", count: $updateStep, step: 5) |
|
|
|
|
} label: { |
|
|
|
|
Text("Décalage") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
RowButtonView("Décaler les horaires", role: .destructive) { |
|
|
|
|
_updateDate() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} footer: { |
|
|
|
|
Text("décale CHAQUE horaire du nombre de minutes indiqué") |
|
|
|
|
.foregroundStyle(.logoRed) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VStack { |
|
|
|
|
StepAdjusterView(step: $updateStep) |
|
|
|
|
Divider() |
|
|
|
|
StepAdjusterView(step: $updateStep, time: 10) |
|
|
|
|
ForEach(selectedFormats, id: \.self) { matchFormat in |
|
|
|
|
DateAdjusterView(date: $dateToUpdate, matchFormat: matchFormat) |
|
|
|
|
Divider() |
|
|
|
|
StepAdjusterView(step: $updateStep, matchFormat: matchFormat) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -325,7 +360,10 @@ struct PlanningView: View { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
.navigationTitle("Modification de la date") |
|
|
|
|
.onChange(of: updateStep, { oldValue, newValue in |
|
|
|
|
dateToUpdate.addTimeInterval(TimeInterval((newValue - oldValue) * 60)) |
|
|
|
|
}) |
|
|
|
|
.navigationTitle("Modifier l'horaire") |
|
|
|
|
.navigationBarTitleDisplayMode(.inline) |
|
|
|
|
.toolbarBackground(.visible, for: .navigationBar) |
|
|
|
|
.toolbar(content: { |
|
|
|
|
@ -334,25 +372,34 @@ struct PlanningView: View { |
|
|
|
|
dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ToolbarItem(placement: .topBarTrailing) { |
|
|
|
|
Button("Valider") { |
|
|
|
|
_updateDate() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _updateDate() { |
|
|
|
|
selectedMatches.forEach { match in |
|
|
|
|
if match.hasStarted() || match.hasEnded() { |
|
|
|
|
match.plannedStartDate?.addTimeInterval(TimeInterval(updateStep * 60)) |
|
|
|
|
} else { |
|
|
|
|
match.startDate?.addTimeInterval(TimeInterval(updateStep * 60)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let groupByTournaments = selectedMatches.grouped { match in |
|
|
|
|
match.currentTournament() |
|
|
|
|
} |
|
|
|
|
groupByTournaments.forEach { tournament, matches in |
|
|
|
|
tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
|
|
|
} |
|
|
|
|
dismiss() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _setDate() { |
|
|
|
|
selectedMatches.forEach { match in |
|
|
|
|
if match.hasStarted() || match.hasEnded() { |
|
|
|
|
match.plannedStartDate = dateToUpdate |
|
|
|
|
} else { |
|
|
|
|
let hasStarted = match.currentTournament()?.hasStarted() == true |
|
|
|
|
match.startDate = dateToUpdate |
|
|
|
|
if hasStarted { |
|
|
|
|
match.plannedStartDate = dateToUpdate |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -378,7 +425,10 @@ struct PlanningView: View { |
|
|
|
|
let keys: [Date] |
|
|
|
|
let timeSlots: [Date: [Match]] |
|
|
|
|
let selectedDay: Date? |
|
|
|
|
|
|
|
|
|
@Binding var selectedIds: Set<String> |
|
|
|
|
@State private var selectAll: Bool = false |
|
|
|
|
@Binding var matchesForUpdateSheet: [Match] |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
Section { |
|
|
|
|
ForEach(keys, id: \.self) { key in |
|
|
|
|
@ -386,12 +436,22 @@ struct PlanningView: View { |
|
|
|
|
key: key, |
|
|
|
|
matches: timeSlots[key]?.sorted( |
|
|
|
|
by: filterOption == .byDefault |
|
|
|
|
? \.computedOrder : \.courtIndexForSorting) ?? [] |
|
|
|
|
? \.computedOrder : \.courtIndexForSorting) ?? [], matchesForUpdateSheet: $matchesForUpdateSheet |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
.onMove(perform: enableMove ? moveSection : nil) |
|
|
|
|
} header: { |
|
|
|
|
HeaderView(day: day, timeSlots: timeSlots) |
|
|
|
|
if editMode?.wrappedValue == .active { |
|
|
|
|
HStack { |
|
|
|
|
Spacer() |
|
|
|
|
FooterButtonView(selectAll ? "Tout desélectionner" : "Tout sélectionner") { |
|
|
|
|
selectAll.toggle() |
|
|
|
|
} |
|
|
|
|
.textCase(nil) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
HeaderView(day: day, timeSlots: timeSlots) |
|
|
|
|
} |
|
|
|
|
} footer: { |
|
|
|
|
VStack(alignment: .leading) { |
|
|
|
|
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { |
|
|
|
|
@ -403,6 +463,16 @@ struct PlanningView: View { |
|
|
|
|
CourtOptionsView(timeSlots: timeSlots, underlined: true) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.onChange(of: selectAll, { oldValue, newValue in |
|
|
|
|
if oldValue == false, newValue == true { |
|
|
|
|
selectedIds = Set(timeSlots.filter({ keys.contains($0.key) }).values.flatMap({ values in values.compactMap({ match in match.stringId }) })) |
|
|
|
|
} else if oldValue == true, newValue == false { |
|
|
|
|
selectedIds.removeAll() |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.onChange(of: editMode?.wrappedValue) { oldValue, newValue in |
|
|
|
|
selectAll = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func moveSection(from source: IndexSet, to destination: Int) { |
|
|
|
|
@ -450,7 +520,7 @@ struct PlanningView: View { |
|
|
|
|
let matches: [Match] |
|
|
|
|
|
|
|
|
|
@State private var isExpanded: Bool = false |
|
|
|
|
@State private var showDateUpdateView: Bool = false |
|
|
|
|
@Binding var matchesForUpdateSheet: [Match] |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
if !matches.isEmpty { |
|
|
|
|
@ -462,21 +532,23 @@ struct PlanningView: View { |
|
|
|
|
} label: { |
|
|
|
|
TimeSlotHeaderView(key: key, matches: matches) |
|
|
|
|
} |
|
|
|
|
.onChange(of: editMode?.wrappedValue, { oldValue, newValue in |
|
|
|
|
if oldValue == .inactive, newValue == .active, isExpanded == false { |
|
|
|
|
isExpanded = true |
|
|
|
|
} else if oldValue == .active, newValue == .inactive, isExpanded == true { |
|
|
|
|
isExpanded = false |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.contextMenu { |
|
|
|
|
PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false) |
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
|
showDateUpdateView = true |
|
|
|
|
matchesForUpdateSheet = matches |
|
|
|
|
} label: { |
|
|
|
|
Text("Modifier la date") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
.sheet(isPresented: $showDateUpdateView, onDismiss: { |
|
|
|
|
}) { |
|
|
|
|
PlanningView.DateUpdateView(selectedMatches: matches) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// .onChange(of: editMode?.wrappedValue) { |
|
|
|
|
// if editMode?.wrappedValue == .active, isExpanded == false { |
|
|
|
|
// isExpanded = true |
|
|
|
|
@ -500,6 +572,7 @@ struct PlanningView: View { |
|
|
|
|
} label: { |
|
|
|
|
MatchRowView(match: match) |
|
|
|
|
} |
|
|
|
|
.listRowView(isActive: match.hasStarted(), color: .green, hideColorVariation: true) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|