multistore
Razmig Sarkissian 2 years ago
parent 355833d808
commit 26f8383981
  1. 15
      PadelClub/ViewModel/MatchScheduler.swift
  2. 2
      PadelClub/Views/Components/BarButtonView.swift
  3. 16
      PadelClub/Views/Planning/Components/DateUpdateManagerView.swift
  4. 17
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  5. 9
      PadelClub/Views/Planning/GroupStageScheduleEditorView.swift
  6. 2
      PadelClub/Views/Planning/LoserRoundScheduleEditorView.swift
  7. 10
      PadelClub/Views/Planning/LoserRoundStepScheduleEditorView.swift
  8. 2
      PadelClub/Views/Planning/MatchScheduleEditorView.swift
  9. 9
      PadelClub/Views/Planning/PlanningSettingsView.swift
  10. 11
      PadelClub/Views/Planning/PlanningView.swift
  11. 8
      PadelClub/Views/Planning/RoundScheduleEditorView.swift
  12. 1
      PadelClub/Views/Planning/SchedulerView.swift
  13. 40
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  14. 2
      PadelClub/Views/Tournament/TournamentView.swift

@ -58,6 +58,7 @@ enum MatchSchedulerOption: Hashable {
case randomizeCourts
case rotationDifferenceIsImportant
case shouldHandleUpperRoundSlice
case shouldEndRoundBeforeStartingNext
}
class MatchScheduler {
@ -69,6 +70,10 @@ class MatchScheduler {
var upperBracketRotationDifference: Int = 1
var courtsUnavailability: [DateInterval]? = nil
func shouldEndRoundBeforeStartingNext() -> Bool {
options.contains(.shouldEndRoundBeforeStartingNext)
}
func shouldHandleUpperRoundSlice() -> Bool {
options.contains(.shouldHandleUpperRoundSlice)
}
@ -464,11 +469,19 @@ class MatchScheduler {
let upperRounds = tournament.rounds()
let allMatches = tournament.allMatches()
let rounds = upperRounds.map {
var rounds = [Round]()
if shouldEndRoundBeforeStartingNext() {
rounds = upperRounds.flatMap {
[$0] + $0.loserRoundsAndChildren()
}
} else {
rounds = upperRounds.map {
$0
} + upperRounds.flatMap {
$0.loserRoundsAndChildren()
}
}
var flattenedMatches = rounds.flatMap { round in
round._matches().filter({ $0.disabled == false }).sorted(by: \.index)

@ -28,7 +28,7 @@ struct BarButtonView: View {
Image(systemName: icon)
.resizable()
.scaledToFit()
.frame(minHeight: 28)
.frame(minHeight: 36)
}
.labelStyle(.iconOnly)
}

@ -20,14 +20,24 @@ struct DateUpdateManagerView: View {
@Binding var startDate: Date
@State private var dateUpdated: Bool = false
var duration: Int?
var validateAction: () -> Void
var body: some View {
HStack {
Menu {
Text("à demain 9h")
Text("à la prochaine rotation")
Text("à la précédente rotation")
Button("à demain 9h") {
startDate = startDate.tomorrowAtNine
}
if let duration {
Button("à la prochaine rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * 60)
}
Button("à la précédente rotation") {
startDate = startDate.addingTimeInterval(Double(duration) * -60)
}
}
} label: {
Text("décaler")
.underline()

@ -17,6 +17,7 @@ struct CourtAvailabilitySettingsView: View {
@State private var courtIndex: Int = 0
@State private var startDate: Date = Date()
@State private var endDate: Date = Date()
@State private var editingSlot: DateInterval?
var courtsUnavailability: [Int: [DateInterval]] {
let groupedBy = Dictionary(grouping: event.courtsUnavailability, by: { dateInterval in
@ -55,6 +56,7 @@ struct CourtAvailabilitySettingsView: View {
try? dataStore.dateIntervals.addOrUpdate(instance: duplicatedDateInterval)
}
Button("éditer") {
editingSlot = dateInterval
courtIndex = dateInterval.courtIndex
startDate = dateInterval.startDate
endDate = dateInterval.endDate
@ -86,8 +88,9 @@ struct CourtAvailabilitySettingsView: View {
}
}
.overlay {
if courtsUnavailability.isEmpty {
ContentUnavailableView {
Label("Tous les terrains sont disponibles", systemImage: "checkmark.circle.fill")
Label("Tous les terrains sont disponibles", systemImage: "checkmark.circle.fill").tint(.green)
} description: {
Text("Vous pouvez précisez l'indisponibilité d'une ou plusieurs terrains, que ce soit pour une journée entière ou un créneau précis.")
} actions: {
@ -96,9 +99,12 @@ struct CourtAvailabilitySettingsView: View {
}
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
BarButtonView("Ajouter une indisponibilité", icon: "plus.circle.fill") {
startDate = tournament.startDate
endDate = tournament.startDate.addingTimeInterval(5400)
showingPopover = true
}
}
@ -119,14 +125,21 @@ struct CourtAvailabilitySettingsView: View {
} footer: {
FooterButtonView("jour entier") {
startDate = startDate.startOfDay
endDate = endDate.endOfDay()
endDate = startDate.endOfDay()
}
}
}
.toolbar {
ButtonValidateView {
if editingSlot == nil {
let dateInterval = DateInterval(event: event.id, courtIndex: courtIndex, startDate: startDate, endDate: endDate)
try? dataStore.dateIntervals.addOrUpdate(instance: dateInterval)
} else {
editingSlot?.courtIndex = courtIndex
editingSlot?.endDate = endDate
editingSlot?.startDate = startDate
try? dataStore.dateIntervals.addOrUpdate(instance: editingSlot!)
}
showingPopover = false
}
}

@ -9,6 +9,7 @@ import SwiftUI
struct GroupStageScheduleEditorView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
var groupStage: GroupStage
@State private var startDate: Date
@State private var dateUpdated: Bool = false
@ -33,17 +34,11 @@ struct GroupStageScheduleEditorView: View {
} header: {
Text(groupStage.groupStageTitle())
} footer: {
DateUpdateManagerView(startDate: $startDate) {
DateUpdateManagerView(startDate: $startDate, duration: groupStage.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
groupStage.startDate = startDate
_save()
}
}
NavigationLink {
GroupStageView(groupStage: groupStage)
} label: {
Text("Voir la poule")
}
}
.onChange(of: groupStage.matchFormat) {
_save()

@ -34,7 +34,7 @@ struct LoserRoundScheduleEditorView: View {
} header: {
Text("Classement " + upperRound.roundTitle())
} footer: {
DateUpdateManagerView(startDate: $startDate) {
DateUpdateManagerView(startDate: $startDate, duration: matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
}

@ -51,9 +51,17 @@ struct LoserRoundStepScheduleEditorView: View {
} header: {
Text(round.selectionLabel())
} footer: {
DateUpdateManagerView(startDate: $startDate) {
HStack {
DateUpdateManagerView(startDate: $startDate, duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
if round.startDate != nil {
FooterButtonView("retirer l'horaire") {
round.startDate = nil
}
}
}
}
.headerProminence(.increased)
}

@ -29,7 +29,7 @@ struct MatchScheduleEditorView: View {
Text(match.matchTitle())
}
} footer: {
DateUpdateManagerView(startDate: $startDate) {
DateUpdateManagerView(startDate: $startDate, duration: match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
}

@ -22,6 +22,7 @@ struct PlanningSettingsView: View {
@State private var isScheduling: Bool = false
@State private var schedulingDone: Bool = false
@State private var showOptions: Bool = false
@State private var shouldEndBeforeStartNext: Bool = true
init(tournament: Tournament) {
self.tournament = tournament
@ -143,6 +144,10 @@ struct PlanningSettingsView: View {
Text("Équilibrer les matchs d'une manche sur plusieurs tours")
}
Toggle(isOn: $shouldEndBeforeStartNext) {
Text("Finir une manche et les matchs de classements avant de continuer")
}
Toggle(isOn: $upperBracketBreakTime) {
Text("Tableau : tenir compte des pauses")
}
@ -181,6 +186,10 @@ struct PlanningSettingsView: View {
matchScheduler.courtsUnavailability = tournament.eventObject?.courtsUnavailability
matchScheduler.options.removeAll()
if shouldEndBeforeStartNext {
matchScheduler.options.insert(.shouldEndRoundBeforeStartingNext)
}
if randomCourtDistribution {
matchScheduler.options.insert(.randomizeCourts)
}

@ -55,7 +55,12 @@ struct PlanningView: View {
}
}
} header: {
Text(day.formatted(.dateTime.day().weekday().month().year()))
HStack {
Text(day.formatted(.dateTime.day().weekday().month()))
Spacer()
let count = _matchesCount(inDayInt: day.dayInt)
Text(count.formatted() + " match" + count.pluralSuffix)
}
}
.headerProminence(.increased)
}
@ -63,6 +68,10 @@ struct PlanningView: View {
.navigationTitle("Programmation")
}
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(matches.count.formatted() + " match" + matches.count.pluralSuffix)

@ -29,18 +29,16 @@ struct RoundScheduleEditorView: View {
}
} footer: {
HStack {
DateUpdateManagerView(startDate: $startDate) {
DateUpdateManagerView(startDate: $startDate, duration: round.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) {
_updateSchedule()
}
Spacer()
if let roundStartDate = round.startDate {
Button("horaire automatique") {
if round.startDate != nil {
FooterButtonView("retirer l'horaire") {
round.startDate = nil
}
.underline()
.buttonStyle(.borderless)
}
}
}

@ -50,6 +50,7 @@ struct SchedulerView: View {
}
else if let groupStage = schedulable as? GroupStage {
GroupStageScheduleEditorView(groupStage: groupStage)
.environment(tournament)
}
}
.navigationTitle(schedulable.titleLabel())

@ -30,6 +30,7 @@ struct InscriptionManagerView: View {
@State private var currentRankSourceDate: Date?
@State private var confirmUpdateRank = false
@State private var selectionSearchField: String?
@State private var autoSelect: Bool = false
let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
@ -279,6 +280,7 @@ struct InscriptionManagerView: View {
await MainActor.run {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
pasteString = first
autoSelect = true
}
}
}
@ -563,6 +565,24 @@ struct InscriptionManagerView: View {
private func _buildingTeamView() -> some View {
List(selection: $createdPlayerIds) {
if let pasteString {
Section {
Text(pasteString)
} footer: {
HStack {
Text("contenu du presse-papier")
Spacer()
Button("effacer", role: .destructive) {
self.pasteString = nil
self.createdPlayers.removeAll()
self.createdPlayerIds.removeAll()
}
.buttonStyle(.borderless)
}
}
}
Section {
ForEach(createdPlayerIds.sorted(), id: \.self) { id in
if let p = createdPlayers.first(where: { $0.id == id }) {
@ -591,22 +611,6 @@ struct InscriptionManagerView: View {
}
if let pasteString {
Section {
Text(pasteString)
} footer: {
HStack {
Text("contenu du presse-papier")
Spacer()
Button("effacer", role: .destructive) {
self.pasteString = nil
self.createdPlayers.removeAll()
self.createdPlayerIds.removeAll()
}
.buttonStyle(.borderless)
}
}
if fetchPlayers.isEmpty {
ContentUnavailableView {
Label("Aucun résultat", systemImage: "person.2.slash")
@ -634,13 +638,13 @@ struct InscriptionManagerView: View {
}
}
.onReceive(fetchPlayers.publisher.count()) { _ in // <-- here
if let pasteString, count == 2 {
if let pasteString, count == 2, autoSelect == true {
fetchPlayers.filter { $0.hitForSearch(pasteString) >= hitTarget }.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }).forEach { player in
createdPlayerIds.insert(player.license!)
}
autoSelect = false
}
}
.environment(\.editMode, Binding.constant(EditMode.active))
}

@ -44,7 +44,7 @@ struct TournamentView: View {
}
}
} footer: {
if tournament.inscriptionClosed() {
if tournament.inscriptionClosed() == false {
Button {
tournament.lockRegistration()
_save()

Loading…
Cancel
Save