fix planning stuff

newoffer2025
Razmig Sarkissian 5 months ago
parent 0a3606916b
commit 87c7c074d3
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 10
      PadelClub/Views/Match/Components/MatchDateView.swift
  3. 6
      PadelClub/Views/Match/MatchDetailView.swift
  4. 70
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  5. 149
      PadelClub/Views/Planning/PlanningView.swift

@ -3129,7 +3129,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.38; MARKETING_VERSION = 1.2.39;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3175,7 +3175,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.38; MARKETING_VERSION = 1.2.39;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -46,7 +46,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = currentDate match.updateStartDate(currentDate, keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -55,7 +55,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: 5, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -64,7 +64,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: 15, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -73,7 +73,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -96,7 +96,7 @@ struct MatchDateView: View {
} }
Divider() Divider()
Button("Retirer l'horaire") { Button("Retirer l'horaire") {
match.cleanScheduleAndSave() match.updateStartDate(nil, keepPlannedStartDate: true)
} }
} label: { } label: {
label label

@ -329,10 +329,7 @@ struct MatchDetailView: View {
} }
} }
Button(role: .destructive) { Button(role: .destructive) {
match.startDate = nil match.updateStartDate(nil, keepPlannedStartDate: true)
match.endDate = nil
match.confirmed = false
save()
} label: { } label: {
Text("Supprimer l'horaire") Text("Supprimer l'horaire")
} }
@ -348,6 +345,7 @@ struct MatchDetailView: View {
} }
Divider() Divider()
Button(role: .destructive) { Button(role: .destructive) {
match.updateStartDate(nil, keepPlannedStartDate: false)
match.resetTeamScores(outsideOf: []) match.resetTeamScores(outsideOf: [])
match.resetMatch() match.resetMatch()
match.confirmed = false match.confirmed = false

@ -262,20 +262,29 @@ struct DateAdjusterView: View {
var body: some View { var body: some View {
HStack(spacing: 4) { HStack(spacing: 4) {
if let matchFormat { if let matchFormat {
_createButton(label: "-\(matchFormat.defaultEstimatedDuration)m", timeOffset: -matchFormat.defaultEstimatedDuration, component: .minute) _createButton(label: "-\(matchFormat.getEstimatedDuration())m", timeOffset: -matchFormat.getEstimatedDuration(), component: .minute)
_createButton(label: "+\(matchFormat.defaultEstimatedDuration)m", timeOffset: +matchFormat.defaultEstimatedDuration, component: .minute) Divider()
_createButton(label: "+\(matchFormat.getEstimatedDuration())m", timeOffset: +matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute) _createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute)
Divider()
_createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute) _createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute)
} else if let time { } else if let time {
_createButton(label: "-\(time)m", timeOffset: -time, component: .minute) _createButton(label: "-\(time)m", timeOffset: -time, component: .minute)
Divider()
_createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute) _createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute)
Divider()
_createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute) _createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute)
Divider()
_createButton(label: "+\(time)m", timeOffset: time, component: .minute) _createButton(label: "+\(time)m", timeOffset: time, component: .minute)
} else { } else {
_createButton(label: "-1h", timeOffset: -1, component: .hour) _createButton(label: "-1h", timeOffset: -60, component: .minute)
Divider()
_createButton(label: "-30m", timeOffset: -30, component: .minute) _createButton(label: "-30m", timeOffset: -30, component: .minute)
Divider()
_createButton(label: "+30m", timeOffset: 30, component: .minute) _createButton(label: "+30m", timeOffset: 30, component: .minute)
_createButton(label: "+1h", timeOffset: 1, component: .hour) Divider()
_createButton(label: "+1h", timeOffset: 60, component: .minute)
} }
} }
.font(.headline) .font(.headline)
@ -287,11 +296,58 @@ struct DateAdjusterView: View {
}) { }) {
Text(label) Text(label)
.lineLimit(1) .lineLimit(1)
.font(.footnote)
.underline()
.frame(maxWidth: .infinity) // Make buttons take equal space .frame(maxWidth: .infinity) // Make buttons take equal space
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderless)
.tint(.master)
}
}
struct StepAdjusterView: View {
@Binding var step: Int
var time: Int?
var matchFormat: MatchFormat?
var body: some View {
HStack(spacing: 4) {
if let matchFormat {
_createButton(label: "-\(matchFormat.getEstimatedDuration())m", timeOffset: -matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "+\(matchFormat.getEstimatedDuration())m", timeOffset: +matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute)
Divider()
_createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute)
} else if let time {
_createButton(label: "-\(time)m", timeOffset: -time, component: .minute)
Divider()
_createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute)
Divider()
_createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute)
Divider()
_createButton(label: "+\(time)m", timeOffset: time, component: .minute)
} else {
_createButton(label: "-1h", timeOffset: -60, component: .minute)
Divider()
_createButton(label: "-30m", timeOffset: -30, component: .minute)
Divider()
_createButton(label: "+30m", timeOffset: 30, component: .minute)
Divider()
_createButton(label: "+1h", timeOffset: 60, component: .minute)
}
}
.font(.headline)
}
private func _createButton(label: String, timeOffset: Int, component: Calendar.Component) -> some View {
Button(action: {
step += timeOffset
}) {
Text(label)
.lineLimit(1)
.frame(maxWidth: .infinity) // Make buttons take equal space
}
.buttonStyle(.borderless)
.tint(.master) .tint(.master)
} }
} }

@ -110,7 +110,7 @@ struct PlanningView: View {
if _confirmationMode() { if _confirmationMode() {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
Button("Annuler") { Button(enableMove ? "Annuler" : "Terminer") {
enableMove = false enableMove = false
enableEditionBinding.wrappedValue = false enableEditionBinding.wrappedValue = false
} }
@ -130,29 +130,27 @@ struct PlanningView: View {
} }
} }
} else { } else {
ToolbarItemGroup(placement: .topBarTrailing) {
Menu {
if notSlots == false { if notSlots == false {
ToolbarItemGroup(placement: .bottomBar) {
HStack {
CourtOptionsView(timeSlots: timeSlots, underlined: false) CourtOptionsView(timeSlots: timeSlots, underlined: false)
Spacer()
Toggle(isOn: $enableMove) { Toggle(isOn: $enableMove) {
Label { Label {
Text("Déplacer") Text("Déplacer un créneau")
} icon: { } icon: {
Image(systemName: "rectangle.2.swap") Image(systemName: "rectangle.2.swap")
} }
} }
.popoverTip(timeSlotMoveOptionTip) .popoverTip(timeSlotMoveOptionTip)
.disabled(_confirmationMode()) .disabled(_confirmationMode())
Spacer()
Toggle(isOn: enableEditionBinding) { Toggle(isOn: enableEditionBinding) {
Text("Modifier") Text("Modifier un horaire")
} }
.disabled(_confirmationMode()) .disabled(_confirmationMode())
} }
}
} Divider()
ToolbarItemGroup(placement: .topBarTrailing) {
Menu { Menu {
Section { Section {
Picker(selection: $showFinishedMatches) { Picker(selection: $showFinishedMatches) {
@ -189,6 +187,7 @@ struct PlanningView: View {
filterOption == .byCourt || showFinishedMatches ? .fill : .none) filterOption == .byCourt || showFinishedMatches ? .fill : .none)
} }
Divider()
Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") { Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") {
let now = Date() let now = Date()
@ -206,6 +205,11 @@ struct PlanningView: View {
} }
} }
.popoverTip(updatePlannedDatesTip) .popoverTip(updatePlannedDatesTip)
} label: {
LabelOptions()
}
} }
} }
}) })
@ -235,7 +239,7 @@ struct PlanningView: View {
@Environment(\.editMode) private var editMode @Environment(\.editMode) private var editMode
@State private var selectedIds = Set<String>() @State private var selectedIds = Set<String>()
@State private var showDateUpdateView: Bool = false @State private var showDateUpdateView: Bool = false
@State private var dateToUpdate: Date = Date() @State private var matchesToUpdate: [Match] = []
let days: [Date] let days: [Date]
let keys: [Date] let keys: [Date]
@ -258,28 +262,34 @@ struct PlanningView: View {
day: day, day: day,
keys: keys.filter({ $0.dayInt == day.dayInt }), keys: keys.filter({ $0.dayInt == day.dayInt }),
timeSlots: timeSlots, timeSlots: timeSlots,
selectedDay: selectedDay selectedDay: selectedDay,
selectedIds: $selectedIds,
matchesForUpdateSheet: $matchesToUpdate
) )
} }
} }
} }
.toolbar(content: { .toolbar(content: {
if editMode?.wrappedValue == .active { if editMode?.wrappedValue == .active {
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .topBarTrailing) {
Button { Button {
showDateUpdateView = true matchesToUpdate = matches.filter({ selectedIds.contains($0.stringId) })
} label: { } label: {
Text("Modifier la date des matchs sélectionnés") Text("Modifier")
} }
.buttonStyle(.borderless)
.disabled(selectedIds.isEmpty) .disabled(selectedIds.isEmpty)
} }
} }
}) })
.onChange(of: matchesToUpdate, { oldValue, newValue in
showDateUpdateView = matchesToUpdate.count > 0
})
.sheet(isPresented: $showDateUpdateView, onDismiss: { .sheet(isPresented: $showDateUpdateView, onDismiss: {
selectedIds.removeAll() selectedIds.removeAll()
matchesToUpdate = []
}) { }) {
let selectedMatches = matches.filter({ selectedIds.contains($0.stringId) }) DateUpdateView(selectedMatches: matchesToUpdate)
DateUpdateView(selectedMatches: selectedMatches)
} }
} }
} }
@ -290,6 +300,7 @@ struct PlanningView: View {
let selectedMatches: [Match] let selectedMatches: [Match]
let selectedFormats: [MatchFormat] let selectedFormats: [MatchFormat]
@State private var dateToUpdate: Date @State private var dateToUpdate: Date
@State private var updateStep: Int = 0
init(selectedMatches: [Match]) { init(selectedMatches: [Match]) {
self.selectedMatches = selectedMatches self.selectedMatches = selectedMatches
@ -306,13 +317,37 @@ struct PlanningView: View {
DatePicker(selection: $dateToUpdate) { DatePicker(selection: $dateToUpdate) {
Text(dateToUpdate.formatted(.dateTime.weekday(.wide))).font(.headline) 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 { Section {
DateAdjusterView(date: $dateToUpdate) LabeledContent {
DateAdjusterView(date: $dateToUpdate, time: 10) 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 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) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar(content: { .toolbar(content: {
@ -334,25 +372,34 @@ struct PlanningView: View {
dismiss() dismiss()
} }
} }
ToolbarItem(placement: .topBarTrailing) {
Button("Valider") {
_updateDate()
}
}
}) })
} }
} }
private func _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 selectedMatches.forEach { match in
if match.hasStarted() || match.hasEnded() { if match.hasStarted() || match.hasEnded() {
match.plannedStartDate = dateToUpdate match.plannedStartDate = dateToUpdate
} else { } else {
let hasStarted = match.currentTournament()?.hasStarted() == true
match.startDate = dateToUpdate match.startDate = dateToUpdate
if hasStarted {
match.plannedStartDate = dateToUpdate
}
} }
} }
@ -378,6 +425,9 @@ struct PlanningView: View {
let keys: [Date] let keys: [Date]
let timeSlots: [Date: [Match]] let timeSlots: [Date: [Match]]
let selectedDay: Date? let selectedDay: Date?
@Binding var selectedIds: Set<String>
@State private var selectAll: Bool = false
@Binding var matchesForUpdateSheet: [Match]
var body: some View { var body: some View {
Section { Section {
@ -386,12 +436,22 @@ struct PlanningView: View {
key: key, key: key,
matches: timeSlots[key]?.sorted( matches: timeSlots[key]?.sorted(
by: filterOption == .byDefault by: filterOption == .byDefault
? \.computedOrder : \.courtIndexForSorting) ?? [] ? \.computedOrder : \.courtIndexForSorting) ?? [], matchesForUpdateSheet: $matchesForUpdateSheet
) )
} }
.onMove(perform: enableMove ? moveSection : nil) .onMove(perform: enableMove ? moveSection : nil)
} header: { } header: {
if editMode?.wrappedValue == .active {
HStack {
Spacer()
FooterButtonView(selectAll ? "Tout desélectionner" : "Tout sélectionner") {
selectAll.toggle()
}
.textCase(nil)
}
} else {
HeaderView(day: day, timeSlots: timeSlots) HeaderView(day: day, timeSlots: timeSlots)
}
} footer: { } footer: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
@ -403,6 +463,16 @@ struct PlanningView: View {
CourtOptionsView(timeSlots: timeSlots, underlined: true) 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) { func moveSection(from source: IndexSet, to destination: Int) {
@ -450,7 +520,7 @@ struct PlanningView: View {
let matches: [Match] let matches: [Match]
@State private var isExpanded: Bool = false @State private var isExpanded: Bool = false
@State private var showDateUpdateView: Bool = false @Binding var matchesForUpdateSheet: [Match]
var body: some View { var body: some View {
if !matches.isEmpty { if !matches.isEmpty {
@ -462,21 +532,23 @@ struct PlanningView: View {
} label: { } label: {
TimeSlotHeaderView(key: key, matches: matches) 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 { .contextMenu {
PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false) PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false)
Button { Button {
showDateUpdateView = true matchesForUpdateSheet = matches
} label: { } label: {
Text("Modifier la date") Text("Modifier la date")
} }
} }
.sheet(isPresented: $showDateUpdateView, onDismiss: {
}) {
PlanningView.DateUpdateView(selectedMatches: matches)
}
// .onChange(of: editMode?.wrappedValue) { // .onChange(of: editMode?.wrappedValue) {
// if editMode?.wrappedValue == .active, isExpanded == false { // if editMode?.wrappedValue == .active, isExpanded == false {
// isExpanded = true // isExpanded = true
@ -500,6 +572,7 @@ struct PlanningView: View {
} label: { } label: {
MatchRowView(match: match) MatchRowView(match: match)
} }
.listRowView(isActive: match.hasStarted(), color: .green, hideColorVariation: true)
} }
} }
} }

Loading…
Cancel
Save