multistore
Razmig Sarkissian 2 years ago
parent 4b4f2c74be
commit 4abd2c5d48
  1. 25
      PadelClub.xcodeproj/project.pbxproj
  2. 26
      PadelClub/Data/Event.swift
  3. 23
      PadelClub/Data/MatchScheduler.swift
  4. 6
      PadelClub/Data/Tournament.swift
  5. 26
      PadelClub/Utils/Tips.swift
  6. 1
      PadelClub/ViewModel/NavigationViewModel.swift
  7. 1
      PadelClub/ViewModel/Screen.swift
  8. 54
      PadelClub/Views/Cashier/Event/EventSettingsView.swift
  9. 91
      PadelClub/Views/Cashier/Event/EventTournamentsView.swift
  10. 76
      PadelClub/Views/Cashier/Event/EventView.swift
  11. 18
      PadelClub/Views/Cashier/PlayerListView.swift
  12. 20
      PadelClub/Views/Event/EventCreationView.swift
  13. 3
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  14. 1
      PadelClub/Views/Navigation/MainView.swift
  15. 50
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  16. 3
      PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift
  17. 19
      PadelClub/Views/Tournament/TournamentInitView.swift
  18. 43
      PadelClub/Views/Tournament/TournamentView.swift

@ -108,7 +108,6 @@
FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; }; FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; };
FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; }; FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; };
FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; }; FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; };
FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627E2BCF9432000C4809 /* PlayerListView.swift */; };
FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; };
FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; };
FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; };
@ -229,6 +228,9 @@
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; };
FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; };
FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; };
FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; };
FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; };
FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; };
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
@ -408,7 +410,6 @@
FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = "<group>"; }; FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = "<group>"; };
FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCustomizationView.swift; sourceTree = "<group>"; }; FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCustomizationView.swift; sourceTree = "<group>"; };
FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CashierSettingsView.swift; sourceTree = "<group>"; }; FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CashierSettingsView.swift; sourceTree = "<group>"; };
FF11627E2BCF9432000C4809 /* PlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerListView.swift; sourceTree = "<group>"; };
FF1162802BCF945C000C4809 /* TournamentCashierView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentCashierView.swift; sourceTree = "<group>"; }; FF1162802BCF945C000C4809 /* TournamentCashierView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentCashierView.swift; sourceTree = "<group>"; };
FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditablePlayerView.swift; sourceTree = "<group>"; }; FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditablePlayerView.swift; sourceTree = "<group>"; };
FF1162842BD00279000C4809 /* PlayerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDetailView.swift; sourceTree = "<group>"; }; FF1162842BD00279000C4809 /* PlayerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDetailView.swift; sourceTree = "<group>"; };
@ -529,6 +530,9 @@
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; }; FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = "<group>"; };
FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = "<group>"; }; FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = "<group>"; };
FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; }; FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; };
FFBF41812BF73EB3001B24CB /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; };
FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTournamentsView.swift; sourceTree = "<group>"; };
FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSettingsView.swift; sourceTree = "<group>"; };
FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; };
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
@ -722,6 +726,7 @@
FF9267FD2BCE94520080F940 /* Calling */, FF9267FD2BCE94520080F940 /* Calling */,
FFF964512BC2628600EEF017 /* Planning */, FFF964512BC2628600EEF017 /* Planning */,
FF11627B2BCF937F000C4809 /* Cashier */, FF11627B2BCF937F000C4809 /* Cashier */,
FFBF41802BF73EA2001B24CB /* Event */,
FF3F74F72B919F96004CFE0E /* Tournament */, FF3F74F72B919F96004CFE0E /* Tournament */,
C4A47D882B7BBB5000ADC637 /* Subscription */, C4A47D882B7BBB5000ADC637 /* Subscription */,
C4A47D852B7BA33F00ADC637 /* User */, C4A47D852B7BA33F00ADC637 /* User */,
@ -868,7 +873,6 @@
children = ( children = (
FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */, FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */,
FF9267F72BCE78C70080F940 /* CashierView.swift */, FF9267F72BCE78C70080F940 /* CashierView.swift */,
FF11627E2BCF9432000C4809 /* PlayerListView.swift */,
FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */, FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */,
); );
path = Cashier; path = Cashier;
@ -1147,6 +1151,17 @@
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FFBF41802BF73EA2001B24CB /* Event */ = {
isa = PBXGroup;
children = (
FFBF41812BF73EB3001B24CB /* EventView.swift */,
FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */,
FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */,
);
name = Event;
path = Cashier/Event;
sourceTree = "<group>";
};
FFC83D4B2BB807C200750834 /* Round */ = { FFC83D4B2BB807C200750834 /* Round */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1518,7 +1533,6 @@
FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */, FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */,
FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */, FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */,
C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */,
FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */,
FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */,
FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */,
FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */,
@ -1588,6 +1602,7 @@
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */, FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
FF9268072BCE94D90080F940 /* TournamentCallView.swift in Sources */, FF9268072BCE94D90080F940 /* TournamentCallView.swift in Sources */,
FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */,
@ -1631,6 +1646,7 @@
FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */,
FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */, FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */,
FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */,
FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */,
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
@ -1638,6 +1654,7 @@
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */, FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */,
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */, FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */,
FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */, FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */,
FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */, FFC91B012BD85C2F00B29808 /* Court.swift in Sources */,
FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */, FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */,

@ -38,6 +38,26 @@ class Event: ModelObject, Storable {
// self.loserRoundFormat = loserRoundFormat // self.loserRoundFormat = loserRoundFormat
} }
func eventCourtCount() -> Int {
tournaments.map { $0.courtCount }.max() ?? 2
}
func eventStartDate() -> Date {
tournaments.map { $0.startDate }.min() ?? Date()
}
func eventDayDuration() -> Int {
tournaments.map { $0.dayDuration }.max() ?? 1
}
func eventTitle() -> String {
if let name, name.isEmpty == false {
return name
} else {
return "Événement"
}
}
func clubObject() -> Club? { func clubObject() -> Club? {
guard let club else { return nil } guard let club else { return nil }
return Store.main.findById(club) return Store.main.findById(club)
@ -51,6 +71,12 @@ class Event: ModelObject, Storable {
tournaments.first(where: { $0.isSameBuild(build) }) tournaments.first(where: { $0.isSameBuild(build) })
} }
func tournamentsCourtsUsed() -> [DateInterval] {
tournaments.flatMap({ tournament in
tournament.getPlayedMatchDateIntervals(in: self)
})
}
var courtsUnavailability: [DateInterval] { var courtsUnavailability: [DateInterval] {
Store.main.filter(isIncluded: { $0.event == id }) Store.main.filter(isIncluded: { $0.event == id })
} }

@ -68,7 +68,8 @@ class MatchScheduler : ModelObject, Storable {
} }
var courtsUnavailability: [DateInterval]? { var courtsUnavailability: [DateInterval]? {
tournamentObject()?.eventObject()?.courtsUnavailability guard let event = tournamentObject()?.eventObject() else { return nil }
return event.courtsUnavailability + event.tournamentsCourtsUsed()
} }
var additionalEstimationDuration : Int { var additionalEstimationDuration : Int {
@ -165,6 +166,7 @@ class MatchScheduler : ModelObject, Storable {
var teamsPerRotation = [Int: [String]]() var teamsPerRotation = [Int: [String]]()
var freeCourtPerRotation = [Int: [Int]]() var freeCourtPerRotation = [Int: [Int]]()
var groupLastRotation = [Int: Int]() var groupLastRotation = [Int: Int]()
let courtsUnavailability = courtsUnavailability
while slots.count < flattenedMatches.count { while slots.count < flattenedMatches.count {
teamsPerRotation[rotationIndex] = [] teamsPerRotation[rotationIndex] = []
@ -192,7 +194,7 @@ class MatchScheduler : ModelObject, Storable {
let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60 let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd) let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable { if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable {
return false return false
} else { } else {
@ -344,6 +346,7 @@ class MatchScheduler : ModelObject, Storable {
var _startDate: Date? var _startDate: Date?
var rotationIndex = 0 var rotationIndex = 0
var availableMatchs = flattenedMatches.filter({ $0.startDate == nil }) var availableMatchs = flattenedMatches.filter({ $0.startDate == nil })
let courtsUnavailability = courtsUnavailability
flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in
if _startDate == nil { if _startDate == nil {
@ -414,7 +417,7 @@ class MatchScheduler : ModelObject, Storable {
} }
} }
dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation) dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability)
rotationIndex += 1 rotationIndex += 1
} }
@ -434,7 +437,7 @@ class MatchScheduler : ModelObject, Storable {
return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex) return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex)
} }
func dispatchCourts(availableCourts: Int, courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]]) { func dispatchCourts(availableCourts: Int, courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) {
var matchPerRound = [Int: Int]() var matchPerRound = [Int: Int]()
var minimumTargetedEndDate: Date = rotationStartDate var minimumTargetedEndDate: Date = rotationStartDate
print("dispatchCourts", courts.sorted(), rotationStartDate, rotationIndex) print("dispatchCourts", courts.sorted(), rotationStartDate, rotationIndex)
@ -442,7 +445,7 @@ class MatchScheduler : ModelObject, Storable {
print("trying to find a match for \(courtIndex) in \(rotationIndex)") print("trying to find a match for \(courtIndex) in \(rotationIndex)")
if let first = availableMatchs.first(where: { match in if let first = availableMatchs.first(where: { match in
let roundObject = match.roundObject! let roundObject = match.roundObject!
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
print("courtsUnavailable \(courtsUnavailable)") print("courtsUnavailable \(courtsUnavailable)")
if courtIndex >= availableCourts - courtsUnavailable { if courtIndex >= availableCourts - courtsUnavailable {
return false return false
@ -519,7 +522,7 @@ class MatchScheduler : ModelObject, Storable {
}.sorted(by: \.1).map { $0.0 } }.sorted(by: \.1).map { $0.0 }
} }
dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation) dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability)
} }
} }
@ -610,18 +613,18 @@ class MatchScheduler : ModelObject, Storable {
} }
func courtsUnavailable(startDate: Date, duration: Int) -> Int { func courtsUnavailable(startDate: Date, duration: Int, courtsUnavailability: [DateInterval]?) -> Int {
let endDate = startDate.addingTimeInterval(Double(duration) * 60) let endDate = startDate.addingTimeInterval(Double(duration) * 60)
guard let courtsUnavailability else { return 0 } guard let courtsUnavailability else { return 0 }
let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex }) let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex })
let courts = groupedBy.keys let courts = groupedBy.keys
return courts.filter { return courts.filter {
courtUnavailable(courtIndex: $0, from: startDate, to: endDate) courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability)
}.count }.count
} }
func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool { func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date, source: [DateInterval]) -> Bool {
guard let courtLockedSchedule = courtsUnavailability?.filter({ $0.courtIndex == courtIndex }) else { return true } let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex })
return courtLockedSchedule.anySatisfy({ dateInterval in return courtLockedSchedule.anySatisfy({ dateInterval in
dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate) dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate)
}) })

@ -600,6 +600,12 @@ class Tournament : ModelObject, Storable {
} }
} }
func getPlayedMatchDateIntervals(in event: Event) -> [DateInterval] {
allMatches().filter { $0.courtIndex != nil && $0.startDate != nil }.map { match in
DateInterval(event: event.id, courtIndex: match.courtIndex!, startDate: match.startDate!, endDate: match.estimatedEndDate(additionalEstimationDuration)!)
}
}
func allRoundMatches() -> [Match] { func allRoundMatches() -> [Match] {
allRounds().flatMap { $0._matches() } allRounds().flatMap { $0._matches() }
} }

@ -366,6 +366,32 @@ struct TournamentTVBroadcastTip: Tip {
} }
} }
struct TournamentSelectionTip: Tip {
@Parameter
static var tournamentCount: Int? = nil
var rules: [Rule] {
[
// Define a rule based on the app state.
#Rule(Self.$tournamentCount) {
// Set the conditions for when the tip displays.
return ($0 ?? 1) > 1
}
]
}
var title: Text {
Text("Naviguer entre les épreuves")
}
var message: Text? {
return Text("Vous pouvez appuyer sur la barre de navigation pour accéder à une épreuve de votre événement.")
}
var image: Image? {
Image(systemName: "filemenu.and.selection")
}
}
struct TipStyleModifier: ViewModifier { struct TipStyleModifier: ViewModifier {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme

@ -15,7 +15,6 @@ class NavigationViewModel {
var ongoingPath = NavigationPath() var ongoingPath = NavigationPath()
var selectedTab: TabDestination? var selectedTab: TabDestination?
var agendaDestination: AgendaDestination? = .activity var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament?
var organizerTournament: Tournament? var organizerTournament: Tournament?
func isTournamentAlreadyOpenInOrganizer(_ tournament: Tournament) -> Bool { func isTournamentAlreadyOpenInOrganizer(_ tournament: Tournament) -> Bool {

@ -18,4 +18,5 @@ enum Screen: String, Codable {
case call case call
case rankings case rankings
case broadcast case broadcast
case event
} }

@ -0,0 +1,54 @@
//
// EventSettingsView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/05/2024.
//
import SwiftUI
import LeStorage
struct EventSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@Bindable var event: Event
@State private var eventName: String = ""
init(event: Event) {
self.event = event
_eventName = State(wrappedValue: event.name ?? "")
}
var body: some View {
Form {
Section {
TextField("Nom de l'événement", text: $eventName)
.autocorrectionDisabled()
.keyboardType(.alphabet)
.onSubmit {
if eventName.trimmed.isEmpty == false {
event.name = eventName.trimmed
} else {
event.name = nil
}
_save()
}
} footer: {
if eventName.isEmpty == false {
FooterButtonView("effacer le nom") {
eventName = ""
event.name = nil
_save()
}
}
}
}
}
private func _save() {
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
}
}
}

@ -0,0 +1,91 @@
//
// EventTournamentsView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/05/2024.
//
import SwiftUI
import LeStorage
struct EventTournamentsView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation
let event: Event
@State private var newTournament: Tournament?
var presentTournamentCreationView: Binding<Bool> { Binding(
get: { newTournament != nil },
set: { isPresented in
if isPresented == false {
newTournament = nil
}
}
)}
var body: some View {
let tournaments = event.tournaments
List {
ForEach(tournaments) { tournament in
TournamentCellView(tournament: tournament)
.contextMenu {
Button {
navigation.openTournamentInOrganizer(tournament)
} label: {
Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow")
}
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
BarButtonView("Ajouter une épreuve", icon: "plus.circle.fill") {
let tournament = Tournament.newEmptyInstance()
newTournament = tournament
}
}
}
.headerProminence(.increased)
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(event.eventTitle())
.sheet(isPresented: presentTournamentCreationView) {
if let newTournament {
NavigationStack {
List {
Section {
TournamentConfigurationView(tournament: newTournament)
}
Section {
RowButtonView("Ajouter l'épreuve") {
newTournament.event = event.id
newTournament.courtCount = event.eventCourtCount()
newTournament.startDate = event.eventStartDate()
newTournament.dayDuration = event.eventDayDuration()
newTournament.setupFederalSettings()
do {
try dataStore.tournaments.addOrUpdate(instance: newTournament)
} catch {
Logger.error(error)
}
self.newTournament = nil
}
}
}
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Nouvelle épreuve")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) {
self.newTournament = nil
}
}
}
}
}
}
}
}

@ -0,0 +1,76 @@
//
// EventView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/05/2024.
//
import SwiftUI
import LeStorage
enum EventDestination: Identifiable, Selectable {
case tournaments(Event)
case cashier
var id: String {
return String(describing: self)
}
func selectionLabel() -> String {
switch self {
case .tournaments:
return "Épreuves"
case .cashier:
return "Finance"
}
}
func badgeValue() -> Int? {
switch self {
case .tournaments(let event):
return event.tournaments.count
case .cashier:
return nil
}
}
func badgeValueColor() -> Color? {
return .logoBackground
}
func badgeImage() -> Badge? {
return nil
}
}
struct EventView: View {
let event: Event
@State private var selectedDestination: EventDestination?
func allDestinations() -> [EventDestination] {
[.tournaments(event), .cashier]
}
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination {
case .none:
EventSettingsView(event: event)
.navigationTitle("Réglages")
case .some(let selectedEventDestination):
switch selectedEventDestination {
case .tournaments(let event):
EventTournamentsView(event: event)
case .cashier:
CashierDetailView(tournaments: event.tournaments)
}
}
}
.headerProminence(.increased)
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(event.eventTitle())
}
}

@ -1,18 +0,0 @@
//
// PlayerListView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/04/2024.
//
import SwiftUI
struct PlayerListView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
#Preview {
PlayerListView()
}

@ -64,8 +64,15 @@ struct EventCreationView: View {
} }
TextField("Nom de l'événement", text: $eventName) TextField("Nom de l'événement", text: $eventName)
} header: { .autocorrectionDisabled()
Text("Informations générales") .keyboardType(.alphabet)
LabeledContent {
Text(tournaments.count.formatted())
} label: {
Text("Nombre d'épreuves")
}
} }
Section { Section {
@ -141,15 +148,8 @@ struct EventCreationView: View {
TournamentConfigurationView(tournament: tournament) TournamentConfigurationView(tournament: tournament)
} footer: { } footer: {
if tournaments.count > 1 { if tournaments.count > 1 {
HStack { FooterButtonView("effacer") {
Spacer()
Button(role: .destructive) {
tournaments.removeAll(where: { $0 == tournament }) tournaments.removeAll(where: { $0 == tournament })
} label: {
LabelDelete()
}
.textCase(nil)
.font(.caption)
} }
} }
} }

@ -194,8 +194,7 @@ struct ActivityView: View {
} }
.navigationTitle(TabDestination.activity.title) .navigationTitle(TabDestination.activity.title)
.navigationDestination(for: Tournament.self) { tournament in .navigationDestination(for: Tournament.self) { tournament in
TournamentView() TournamentView(tournament: tournament)
.environment(tournament)
} }
.sheet(isPresented: $presentFilterView) { .sheet(isPresented: $presentFilterView) {
TournamentFilterView(federalDataViewModel: federalDataViewModel) TournamentFilterView(federalDataViewModel: federalDataViewModel)

@ -61,7 +61,6 @@ struct MainView: View {
} }
.id(Store.main.currentUserUUID) .id(Store.main.currentUserUUID)
.onChange(of: Store.main.currentUserUUID) { .onChange(of: Store.main.currentUserUUID) {
navigation.tournament = nil
navigation.path.removeLast(navigation.path.count) navigation.path.removeLast(navigation.path.count)
} }
.environmentObject(dataStore) .environmentObject(dataStore)

@ -12,17 +12,38 @@ struct OngoingView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
var matches: [Match] { var matches: [Match] {
dataStore.matches.filter({ $0.isRunning() }).sorted(by: \.startDate!) dataStore.matches.filter({ $0.startDate != nil && $0.endDate == nil }).sorted(by: \.startDate!)
} }
var body: some View { var body: some View {
@Bindable var navigation = navigation @Bindable var navigation = navigation
NavigationStack(path: $navigation.ongoingPath) { NavigationStack(path: $navigation.ongoingPath) {
let matches = matches
List { List {
ForEach(matches) { match in ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: .feedStyle) Section {
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
} header: {
HStack {
if let roundTitle = match.roundTitle() {
Text(roundTitle)
} }
Text(match.matchTitle(.short))
Spacer()
if let courtName = match.courtName() {
Spacer()
Text(courtName)
} }
}
} footer: {
if let tournament = match.currentTournament() {
Text(tournament.tournamentTitle())
}
}
}
}
.headerProminence(.increased)
.overlay { .overlay {
if matches.isEmpty { if matches.isEmpty {
ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi.")) ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi."))
@ -30,6 +51,31 @@ struct OngoingView: View {
} }
.navigationTitle("En cours") .navigationTitle("En cours")
.toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar) .toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Menu {
Button("Par terrain") {
}
Button("Par date") {
}
//todo
//presentFilterView.toggle()
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.resizable()
.scaledToFit()
.frame(minHeight: 28)
}
//.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none)
}
ToolbarItem(placement: .status) {
if matches.isEmpty == false {
Text("\(matches.count) matche" + matches.count.pluralSuffix)
}
}
}
} }
} }
} }

@ -12,8 +12,7 @@ struct OrganizedTournamentView: View {
var body: some View { var body: some View {
@Bindable var tournament = tournament @Bindable var tournament = tournament
NavigationStack(path: $tournament.navigationPath) { NavigationStack(path: $tournament.navigationPath) {
TournamentView(presentationContext: .organizer) TournamentView(tournament: tournament, presentationContext: .organizer)
.environment(tournament)
// .onChange(of: navigationPath) { // .onChange(of: navigationPath) {
// tournament.navigationPath = navigationPath // tournament.navigationPath = navigationPath
// } // }

@ -8,10 +8,24 @@
import SwiftUI import SwiftUI
struct TournamentInitView: View { struct TournamentInitView: View {
@Environment(Tournament.self) private var tournament: Tournament var tournament: Tournament
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if let event = tournament.eventObject() {
let tournaments = event.tournaments
Section {
NavigationLink(value: Screen.event) {
LabeledContent {
Text(tournaments.count.formatted() + " épreuve" + tournaments.count.pluralSuffix)
} label: {
Text("Gestion de l'événement")
}
}
}
}
Section { Section {
NavigationLink(value: Screen.settings) { NavigationLink(value: Screen.settings) {
LabeledContent { LabeledContent {
@ -51,6 +65,5 @@ struct TournamentInitView: View {
} }
#Preview { #Preview {
TournamentInitView() TournamentInitView(tournament: Tournament.mock())
.environment(Tournament.mock())
} }

@ -7,13 +7,25 @@
import SwiftUI import SwiftUI
import LeStorage import LeStorage
import TipKit
struct TournamentView: View { struct TournamentView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) var navigation: NavigationViewModel @Environment(NavigationViewModel.self) var navigation: NavigationViewModel
@Environment(Tournament.self) var tournament: Tournament @State var tournament: Tournament
var presentationContext: PresentationContext = .agenda var presentationContext: PresentationContext = .agenda
let tournamentSelectionTip = TournamentSelectionTip()
var selectedTournamentId: Binding<String> { Binding(
get: { tournament.id },
set: { id in
if let tournamentFound: Tournament = Store.main.findById(id) {
tournament = tournamentFound
}
}
)}
var lastDataSource: String? { var lastDataSource: String? {
dataStore.appSettings.lastDataSource dataStore.appSettings.lastDataSource
} }
@ -72,7 +84,7 @@ struct TournamentView: View {
Text("todo expliquer cet état") Text("todo expliquer cet état")
} }
case .initial: case .initial:
TournamentInitView() TournamentInitView(tournament: tournament)
case .build: case .build:
TournamentRunningView(tournament: tournament) TournamentRunningView(tournament: tournament)
@ -108,11 +120,27 @@ struct TournamentView: View {
TournamentRankView() TournamentRankView()
case .broadcast: case .broadcast:
BroadcastView() BroadcastView()
case .event:
if let event = tournament.eventObject() {
EventView(event: event)
}
} }
} }
.environment(tournament) .environment(tournament)
}) })
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarTitleMenu {
if let event = tournament.eventObject() {
Picker(selection: selectedTournamentId) {
ForEach(event.tournaments) { tournament in
Text(tournament.tournamentTitle()).tag(tournament.id as String)
}
} label: {
}
}
}
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar { .toolbar {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
@ -121,6 +149,10 @@ struct TournamentView: View {
Text(tournament.formattedDate()) Text(tournament.formattedDate())
.font(.subheadline).foregroundStyle(.secondary) .font(.subheadline).foregroundStyle(.secondary)
} }
.popoverTip(tournamentSelectionTip)
.onAppear {
TournamentSelectionTip.tournamentCount = tournament.eventObject()?.tournaments.count
}
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
@ -135,6 +167,10 @@ struct TournamentView: View {
Divider() Divider()
if tournament.state() == .build { if tournament.state() == .build {
NavigationLink(value: Screen.event) {
Text("Gestion de l'événement")
}
NavigationLink(value: Screen.settings) { NavigationLink(value: Screen.settings) {
LabelSettings() LabelSettings()
} }
@ -169,7 +205,6 @@ struct TournamentView: View {
#Preview { #Preview {
NavigationStack { NavigationStack {
TournamentView(presentationContext: .agenda) TournamentView(tournament: Tournament.mock(), presentationContext: .agenda)
.environment(Tournament.mock())
} }
} }

Loading…
Cancel
Save