diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 875a58c..b716bbf 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -69,6 +69,17 @@ FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8F26382BAD523300650388 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; + FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; + FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263E2BAD7D5C00650388 /* Event.swift */; }; + FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; + FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */; }; + FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; + FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */; }; + FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */; }; + FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */; }; + FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */; }; + FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; }; + FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; @@ -196,6 +207,17 @@ FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8F26352BAD523300650388 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = ""; }; FF8F263A2BAD528600650388 /* EventCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCreationView.swift; sourceTree = ""; }; + FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentConfiguratorView.swift; sourceTree = ""; }; + FF8F263E2BAD7D5C00650388 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; + FF8F26402BADFC8700650388 /* TournamentInitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentInitView.swift; sourceTree = ""; }; + FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSettingsView.swift; sourceTree = ""; }; + FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentDurationManagerView.swift; sourceTree = ""; }; + FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFieldsManagerView.swift; sourceTree = ""; }; + FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFormatSelectionView.swift; sourceTree = ""; }; + FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentLevelPickerView.swift; sourceTree = ""; }; + FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentDatePickerView.swift; sourceTree = ""; }; + FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTypeSelectionView.swift; sourceTree = ""; }; + FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatPickerView.swift; sourceTree = ""; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = ""; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = ""; }; @@ -326,6 +348,7 @@ C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */, C4A47D592B6D383C00ADC637 /* Tournament.swift */, C4A47D622B6D3D6500ADC637 /* Club.swift */, + FF8F263E2BAD7D5C00650388 /* Event.swift */, FF1DC5522BAB354A00FD8220 /* MockData.swift */, FF6EC9012B94799200EA7F5A /* Coredata */, FF6EC9022B9479B900EA7F5A /* Federal */, @@ -423,6 +446,7 @@ isa = PBXGroup; children = ( FF70916B2B91005400AB08DA /* TournamentView.swift */, + FF8F26402BADFC8700650388 /* TournamentInitView.swift */, FF3F74F92B91A018004CFE0E /* Screen */, FF3F74F82B919FB2004CFE0E /* Shared */, ); @@ -444,6 +468,8 @@ FF6EC8FD2B94792300EA7F5A /* Screen.swift */, FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */, FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */, + FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */, + FF8F26522BAE0E4E00650388 /* Components */, ); path = Screen; sourceTree = ""; @@ -487,6 +513,8 @@ FF6EC8FC2B9478C800EA7F5A /* Shared */ = { isa = PBXGroup; children = ( + FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */, + FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */, FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */, FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */, ); @@ -524,10 +552,23 @@ isa = PBXGroup; children = ( FF8F263A2BAD528600650388 /* EventCreationView.swift */, + FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */, ); path = Event; sourceTree = ""; }; + FF8F26522BAE0E4E00650388 /* Components */ = { + isa = PBXGroup; + children = ( + FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */, + FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */, + FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */, + FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */, + FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */, + ); + path = Components; + sourceTree = ""; + }; FFD783FB2B91B919000F62A6 /* Agenda */ = { isa = PBXGroup; children = ( @@ -731,9 +772,12 @@ files = ( C4A47D872B7BA36D00ADC637 /* UserCreationView.swift in Sources */, FF7091662B90F0B000AB08DA /* TabDestination.swift in Sources */, + FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, + FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */, + FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */, FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */, @@ -749,6 +793,7 @@ FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */, + FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */, C4A47D742B72881F00ADC637 /* ClubView.swift in Sources */, C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, @@ -757,10 +802,12 @@ C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */, C4A47D7B2B73C0F900ADC637 /* TournamentV2.swift in Sources */, FF3795662B9399AA004EA093 /* Persistence.swift in Sources */, + FF8F264B2BAE0B4100650388 /* TournamentLevelPickerView.swift in Sources */, C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */, FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */, C4A47D7D2B73CDC300ADC637 /* ClubV1.swift in Sources */, + FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */, FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, @@ -776,16 +823,20 @@ FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */, + FF8F26472BAE0ACB00650388 /* TournamentFieldsManagerView.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, C425D4032B6D249D002A7B48 /* ContentView.swift in Sources */, FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */, + FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */, + FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, C4A47D772B73789100ADC637 /* TournamentV1.swift in Sources */, C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */, + FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */, @@ -795,6 +846,7 @@ FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, + FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */, FFF8ACD42B92392C008466FA /* SourceFileManager.swift in Sources */, diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index 46616a3..e188cdb 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -46,7 +46,7 @@ class Club : ModelObject, Storable, Hashable { } var tournaments: [Tournament] { - return Store.main.filter { $0.club_id == self.id } + return [] } override func deleteDependencies() throws { @@ -73,7 +73,7 @@ extension Club { } func automaticShortName() -> String { - name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) + String(name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines).prefix(10)) } enum AcronymMode: String, CaseIterable { diff --git a/PadelClub/Data/DataStore.swift b/PadelClub/Data/DataStore.swift index 0314a9e..23fd265 100644 --- a/PadelClub/Data/DataStore.swift +++ b/PadelClub/Data/DataStore.swift @@ -17,21 +17,13 @@ class DataStore: ObservableObject { fileprivate(set) var tournaments: StoredCollection fileprivate(set) var clubs: StoredCollection - + fileprivate(set) var events: StoredCollection + fileprivate var _userStorage: OptionalStorage = OptionalStorage(fileName: "user.json") var user: User? { return self._userStorage.item - } - - static let fakeTournaments: [Tournament] = [ - Tournament(name: "P100", club_id: "", category: 0, playerCount: 16), - Tournament(name: "P250", club_id: "", category: 0, playerCount: 24), - Tournament(name: "P25", club_id: "", category: 0, playerCount: 4), - Tournament(name: "P1000", club_id: "", category: 0, playerCount: 8), - Tournament(name: "P500", club_id: "", category: 0, playerCount: 48), - ] - + } func setUser(_ user: User?) { self._userStorage.item = user @@ -47,6 +39,7 @@ class DataStore: ObservableObject { self.clubs = store.registerCollection(synchronized: false) self.tournaments = store.registerCollection(synchronized: false) + self.events = store.registerCollection(synchronized: false) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil) diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift new file mode 100644 index 0000000..692c530 --- /dev/null +++ b/PadelClub/Data/Event.swift @@ -0,0 +1,57 @@ +// +// Event_v2.swift +// Padel Tournament +// +// Created by razmig on 10/03/2024. +// + +import Foundation +import LeStorage +import SwiftUI + +@Observable +class Event: ModelObject, Storable { + static func resourceName() -> String { return "events" } + + var id: String = Store.randomId() + var club: String? + var creationDate: Date = Date() + var name: String? + var courtCount: Int? + var tenupId: String? + var groupStageFormat: Int? + var roundFormat: Int? + var loserRoundFormat: Int? + + internal init(club: String? = nil, name: String? = nil, courtCount: Int? = nil, tenupId: String? = nil, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil) { + self.club = club + self.name = name + self.courtCount = courtCount + self.tenupId = tenupId + self.groupStageFormat = groupStageFormat + self.roundFormat = roundFormat + self.loserRoundFormat = loserRoundFormat + } + + var tournaments: [Tournament] { + Store.main.filter { $0.event == self.id } + } + + override func deleteDependencies() throws { + try Store.main.deleteDependencies(items: self.tournaments) + } +} + +extension Event { + enum CodingKeys: String, CodingKey { + case _id = "id" + case _club = "club" + case _creationDate = "creationDate" + case _name = "name" + case _courtCount = "courtCount" + case _tenupId = "tenupId" + case _groupStageFormat = "groupStageFormat" + case _roundFormat = "roundFormat" + case _loserRoundFormat = "loserRoundFormat" + } +} diff --git a/PadelClub/Data/Migration/ClubV1.swift b/PadelClub/Data/Migration/ClubV1.swift index f73af42..22a253e 100644 --- a/PadelClub/Data/Migration/ClubV1.swift +++ b/PadelClub/Data/Migration/ClubV1.swift @@ -20,7 +20,7 @@ class ClubV1 : ModelObject, Storable, MigrationSource { } var tournaments: [Tournament] { - return Store.main.filter { $0.club_id == self.id } + return [] } override func deleteDependencies() throws { diff --git a/PadelClub/Data/Migration/TournamentV2.swift b/PadelClub/Data/Migration/TournamentV2.swift index 841cc5a..3abd316 100644 --- a/PadelClub/Data/Migration/TournamentV2.swift +++ b/PadelClub/Data/Migration/TournamentV2.swift @@ -30,7 +30,7 @@ class TournamentV2 : ModelObject, Storable, MigrationSource { typealias Destination = Tournament func migrate() -> Tournament { - return Tournament(name: self.name, club_id: self.club_id, category: 0, playerCount: 12) + return Tournament.mock() } } diff --git a/PadelClub/Data/MockData.swift b/PadelClub/Data/MockData.swift index 0df8d6e..a385ec4 100644 --- a/PadelClub/Data/MockData.swift +++ b/PadelClub/Data/MockData.swift @@ -5,6 +5,8 @@ // Created by Razmig Sarkissian on 20/03/2024. // +import Foundation + extension Club { static func mock() -> Club { Club(name: "AUC", acronym: "AUC") @@ -14,3 +16,13 @@ extension Club { Club(name: "", acronym: "") } } + +extension Tournament { + static func mock() -> Tournament { + Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) + } + + static func newEmptyInstance() -> Tournament { + Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) + } +} diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index e476743..3c45892 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -15,27 +15,257 @@ class Tournament : ModelObject, Storable, Hashable { } func hash(into hasher: inout Hasher) { - return hasher.combine(id) + hasher.combine(id) + hasher.combine(event) + hasher.combine(creator) + hasher.combine(courtCount) } + @ObservationIgnored + var undoManager: Int = 0 + static func resourceName() -> String { "tournaments" } var id: String = Store.randomId() - var name: String - var club_id: String - var category: Int - var playerCount: Int - + var event: String? + var creator: String? + var name: String? + var startDate: Date + var endDate: Date? + private(set) var creationDate: Date + var isPrivate: Bool + var groupStageFormat: Int? + var roundFormat: Int? + var loserRoundFormat: Int? + var groupStageSortMode: Int + var groupStageCount: Int + var rankSourceDate: Date? + var dayDuration: Int + var teamCount: Int + var teamSorting: Int + var federalCategory: Int + var federalLevelCategory: Int + var federalAgeCategory: Int + var groupStageCourtCount: Int? + var seedCount: Int + var closedRegistrationDate: Date? + var groupStageAdditionalQualified: Int + var courtCount: Int = 2 + var prioritizeClubMembers: Bool + var qualifiedPerGroupStage: Int + var teamsPerGroupStage: Int + var entryFee: Double? + + internal init(event: String? = nil, creator: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: Int? = nil, roundFormat: Int? = nil, loserRoundFormat: Int? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, groupStageCourtCount: Int? = nil, seedCount: Int = 8, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil) { + self.event = event + self.creator = creator + self.name = name + self.startDate = startDate + self.endDate = endDate + self.creationDate = creationDate + self.isPrivate = isPrivate + self.groupStageFormat = groupStageFormat + self.roundFormat = roundFormat + self.loserRoundFormat = loserRoundFormat + self.groupStageSortMode = groupStageSortMode.rawValue + self.groupStageCount = groupStageCount + self.rankSourceDate = rankSourceDate + self.dayDuration = dayDuration + self.teamCount = teamCount + self.teamSorting = teamSorting.rawValue + self.federalCategory = federalCategory.rawValue + self.federalLevelCategory = federalLevelCategory.rawValue + self.federalAgeCategory = federalAgeCategory.rawValue + self.groupStageCourtCount = groupStageCourtCount + self.seedCount = seedCount + self.closedRegistrationDate = closedRegistrationDate + self.groupStageAdditionalQualified = groupStageAdditionalQualified + self.courtCount = courtCount + self.prioritizeClubMembers = prioritizeClubMembers + self.qualifiedPerGroupStage = qualifiedPerGroupStage + self.teamsPerGroupStage = teamsPerGroupStage + self.entryFee = entryFee + } + @ObservationIgnored var navigationPath: [Screen] = [] - init(name: String, club_id: String, category: Int, playerCount: Int) { - self.name = name - self.club_id = club_id - self.category = category - self.playerCount = playerCount + var rounds: Int { + 4 + } + + func title(_ displayStyle: DisplayStyle = .wide) -> String { + [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ") + } + + func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String { + switch displayStyle { + case .wide: + startDate.formatted(date: Date.FormatStyle.DateStyle.complete, time: Date.FormatStyle.TimeStyle.omitted) + case .short: + startDate.formatted(date: .numeric, time: .omitted) + } + } + + func settingsDescriptionLocalizedLabel() -> String { + [dayDuration.formatted() + " jour\(dayDuration.pluralSuffix)", courtCount.formatted() + " terrain\(courtCount.pluralSuffix)"].joined(separator: ", ") + } +} + +extension Tournament { + func isFree() -> Bool { + entryFee == nil || entryFee == 0 + } +} + +extension Tournament { + var teamSortingType: TeamSortingType { + get { + TeamSortingType(rawValue: teamSorting) ?? tournamentLevel.defaultTeamSortingType + } + set { + teamSorting = newValue.rawValue + } + } + + var matchFormat: MatchFormat { + get { + MatchFormat(rawValue: roundFormat ?? 0) ?? .defaultFormatForMatchType(.bracket) + } + set { + roundFormat = newValue.rawValue + } + } + + var groupStageMatchFormat: MatchFormat { + get { + MatchFormat(rawValue: groupStageFormat ?? 0) ?? .defaultFormatForMatchType(.groupStage) + } + set { + groupStageFormat = newValue.rawValue + } + } + + var loserBracketMatchFormat: MatchFormat { + get { + MatchFormat(rawValue: loserRoundFormat ?? 0) ?? .defaultFormatForMatchType(.loserBracket) + } + set { + loserRoundFormat = newValue.rawValue + } + } + + var groupStageOrderingMode: GroupStageOrderingMode { + get { + GroupStageOrderingMode(rawValue: groupStageSortMode) ?? .random + } + set { + groupStageSortMode = newValue.rawValue + } + } + + var tournamentCategory: TournamentCategory { + get { + TournamentCategory(rawValue: federalCategory) ?? .men + } + set { + federalCategory = newValue.rawValue + } } - var club: Club? { return self.findById(self.club_id) } + var tournamentLevel: TournamentLevel { + get { + TournamentLevel(rawValue: federalLevelCategory) ?? .p100 + } + set { + federalLevelCategory = newValue.rawValue + teamSortingType = newValue.defaultTeamSortingType + groupStageMatchFormat = groupStageSmartMatchFormat() + loserBracketMatchFormat = loserBracketSmartMatchFormat(1) + matchFormat = roundSmartMatchFormat(1) + } + } + + var federalTournamentAge: FederalTournamentAge { + get { + FederalTournamentAge(rawValue: federalAgeCategory) ?? .senior + } + set { + federalAgeCategory = newValue.rawValue + } + } + + func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { + let idx = rounds - roundIndex + let format = tournamentLevel.federalFormatForLoserBracketRound(idx) + if loserBracketMatchFormat.rank > format.rank { + return format + } else { + return loserBracketMatchFormat + } + } + + func groupStageSmartMatchFormat() -> MatchFormat { + let format = tournamentLevel.federalFormatForGroupStage() + if groupStageMatchFormat.rank > format.rank { + return format + } else { + return groupStageMatchFormat + } + } + + func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { + let idx = rounds - roundIndex + let format = tournamentLevel.federalFormatForBracketRound(idx) + if matchFormat.rank > format.rank { + return format + } else { + return matchFormat + } + } +} + +extension Tournament { + enum CodingKeys: String, CodingKey { + case _id = "id" + case _event = "event" + case _creator = "creator" + case _name = "name" + case _startDate = "startDate" + case _endDate = "endDate" + case _creationDate = "creationDate" + case _isPrivate = "isPrivate" + case _groupStageFormat = "groupStageFormat" + case _roundFormat = "roundFormat" + case _loserRoundFormat = "loserRoundFormat" + case _groupStageSortMode = "groupStageSortMode" + case _groupStageCount = "groupStageCount" + case _rankSourceDate = "rankSourceDate" + case _dayDuration = "dayDuration" + case _teamCount = "teamCount" + case _teamSorting = "teamSorting" + case _federalCategory = "federalCategory" + case _federalLevelCategory = "federalLevelCategory" + case _federalAgeCategory = "federalAgeCategory" + case _groupStageCourtCount = "groupStageCourtCount" + case _seedCount = "seedCount" + case _closedRegistrationDate = "closedRegistrationDate" + case _groupStageAdditionalQualified = "groupStageAdditionalQualified" + case _courtCount = "courtCount" + case _prioritizeClubMembers = "prioritizeClubMembers" + case _qualifiedPerGroupStage = "qualifiedPerGroupStage" + case _teamsPerGroupStage = "teamsPerGroupStage" + case _entryFee = "entryFee" + } +} + +extension Tournament { + func state() -> Tournament.State { + .initial + } + + enum State { + case initial + } } diff --git a/PadelClub/Manager/DisplayContext.swift b/PadelClub/Manager/DisplayContext.swift index 2517536..1a22a0d 100644 --- a/PadelClub/Manager/DisplayContext.swift +++ b/PadelClub/Manager/DisplayContext.swift @@ -11,3 +11,8 @@ enum DisplayContext { case addition case edition } + +enum DisplayStyle { + case wide + case short +} diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Manager/PadelRule.swift index ac564a0..a7b0a55 100644 --- a/PadelClub/Manager/PadelRule.swift +++ b/PadelClub/Manager/PadelRule.swift @@ -12,7 +12,7 @@ enum RankSource: Hashable { case ligue case club(assimilation: Bool) - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .national: return "Classement National" @@ -34,20 +34,20 @@ struct TournamentBuild: Hashable, Codable, Identifiable { var japLastName: String? = nil var identifier: String { - level.localizedLabel+":"+category.localizedLabel+":"+age.localizedLabel + level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() } var computedLabel: String { - if age == .senior { return localizedLabel } - return localizedLabel + " " + localizedAge + if age == .senior { return localizedLabel() } + return localizedLabel() + " " + localizedAge } - var localizedLabel: String { - level.localizedLabel + category.localizeLabelShort + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + level.localizedLabel() + category.localizedLabel(.short) } var localizedTitle: String { - level.localizedLabel + " " + category.localizedLabel + level.localizedLabel() + " " + category.localizedLabel() } var localizedAge: String { @@ -58,7 +58,7 @@ struct TournamentBuild: Hashable, Codable, Identifiable { extension TournamentBuild { init?(category: String, level: String, age: FederalTournamentAge = .senior) { - guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLabel == level }) else { return nil } + guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLabel() == level }) else { return nil } var c = category if c.hasPrefix("ME") { @@ -82,7 +82,7 @@ enum FederalTournamentType: String, Hashable, Codable, CaseIterable, Identifiabl case championnatParPaire = "L" var id: String { self.rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .tournoi: return "Tournois" @@ -116,7 +116,7 @@ enum TournamentDifficulty { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .rankS: return "S" @@ -189,7 +189,7 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .a11_12: return "11/12 ans" @@ -213,9 +213,9 @@ enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { case .senior: return "" case .a45, .a55: - return "+" + localizedLabel + return "+" + localizedLabel() default: - return localizedLabel + return localizedLabel() } } } @@ -347,7 +347,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { } - var defaultEntriesSortingType: EntriesSortingType { + var defaultTeamSortingType: TeamSortingType { switch self { case .p25, .p100, .p250: return .inscriptionDate @@ -375,7 +375,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { return String(describing: self).capitalized } @@ -686,17 +686,6 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { } } - var localizeLabelShort: String { - switch self { - case .men: - return "H" - case .women: - return "D" - case .mix: - return "MX" - } - } - var requestLabel: String { switch self { case .men: @@ -719,14 +708,14 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .men: - return "Hommes" + return displayStyle == .wide ? "Hommes" : "H" case .women: - return "Dames" + return displayStyle == .wide ? "Dames" : "D" case .mix: - return "Mixte" + return displayStyle == .wide ? "Mixte" : "MX" } } @@ -754,13 +743,13 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { } -enum BracketOrderingMode: Int, Hashable, Codable, CaseIterable, Identifiable { +enum GroupStageOrderingMode: Int, Hashable, Codable, CaseIterable, Identifiable { case random case snake case swiss var id: Int { self.rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .random: return "Au hasard" @@ -788,7 +777,7 @@ enum TournamentType: Int, Hashable, Codable, CaseIterable, Identifiable { case doubleBrackets var id: Int { self.rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .classic: return "Classique" @@ -811,7 +800,7 @@ enum TeamData: Int, Hashable, Codable, CaseIterable { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .one: return "#1" @@ -1218,7 +1207,7 @@ enum Format: Int, Hashable, Codable { case tiebreakTen case tiebreakFiveTeen - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .normal: return "normal" @@ -1261,7 +1250,7 @@ enum ActionType: Int, Identifiable { self } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .fault: return "Faute" @@ -1294,28 +1283,34 @@ enum ActionType: Int, Identifiable { enum EventType: Int, CaseIterable, Identifiable { case approvedTournament + case friendlyTournament + case simulation case animation var id: Self { self } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .approvedTournament: return "Tournoi homologué" + case .friendlyTournament: + return "Tournoi amical" + case .simulation: + return "Simulation" case .animation: return "Animation" } } } -enum EntriesSortingType: Int, Identifiable, CaseIterable, Hashable { +enum TeamSortingType: Int, Identifiable, CaseIterable, Hashable { case rank = 1 case inscriptionDate = 2 var id: Int { rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .rank: return "Rang" @@ -1334,7 +1329,7 @@ enum PlayersCountRange: Int, CaseIterable { case N28 = 28 case N32 = 32 - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .N8: return "4 à 8" @@ -1372,3 +1367,32 @@ enum RoundLabel { } } +enum AnimationType: Int, CaseIterable, Hashable, Identifiable { + case playerAnimation + case upAndDown + case brawl + + var id: Int { rawValue } + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + switch self { + case .playerAnimation: + return "Par joueur" + case .upAndDown: + return "Montante / Descandante" + case .brawl: + return "Brawl" + } + } + + var descriptionLabel: String { + switch self { + case .playerAnimation: + return "Chaque joueur joue avec quelqu'un de différent à chaque rotation (8 à 12 joueurs)" + case .upAndDown: + return "Les gagnants montent sur le terrain d'à côté, les perdants descendent" + case .brawl: + return "A chaque rotaiton, les gagnants de la rotation pécédente se jouent entre eux" + } + } +} + diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index 3623064..08359b2 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -273,7 +273,7 @@ enum SearchToken: String, CaseIterable, Identifiable { } } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .club: return "Club" @@ -341,7 +341,7 @@ enum DataSet: Int, CaseIterable, Identifiable { case favorite var id: Int { rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .national: return "National" @@ -375,7 +375,7 @@ enum SortOption: Int, CaseIterable, Identifiable { case points var id: Int { self.rawValue } - var localizedLabel: String { + func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { case .name: return "Nom" diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index 45ddf87..ed77f5b 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -52,10 +52,15 @@ struct ClubDetailView: View { .focused($focusedField, equals: ._acronym) .submitLabel(.done) .multilineTextAlignment(.trailing) + .onSubmit(of: .text) { + if club.acronym.count > 10 { + club.acronym = String(club.acronym.prefix(10)) + } + } } } label: { VStack(alignment: .leading, spacing: 0) { - Text("Nom court").foregroundStyle(.secondary).font(.caption) + Text("Nom court (10 caractères max.)").foregroundStyle(.secondary).font(.caption) Menu { Section { ForEach(Club.AcronymMode.allCases, id: \.self) { option in diff --git a/PadelClub/Views/ClubView.swift b/PadelClub/Views/ClubView.swift index 37eb36f..8057a73 100644 --- a/PadelClub/Views/ClubView.swift +++ b/PadelClub/Views/ClubView.swift @@ -13,7 +13,7 @@ struct ClubView: View { var body: some View { List(club.tournaments) { tournament in - Text(tournament.name) + Text(tournament.title()) }.navigationTitle(club.name) } } diff --git a/PadelClub/Views/Event/EventCreationView.swift b/PadelClub/Views/Event/EventCreationView.swift index 1d99939..9651a16 100644 --- a/PadelClub/Views/Event/EventCreationView.swift +++ b/PadelClub/Views/Event/EventCreationView.swift @@ -8,11 +8,143 @@ import SwiftUI struct EventCreationView: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var dataStore: DataStore + @State private var eventType: EventType = .approvedTournament + @State private var animationType: AnimationType = .upAndDown + @State private var startingDate: Date = Date() + @State private var duration: Int = 3 + @State private var eventName: String = "" + @State var tournaments: [Tournament] = [] + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + NavigationStack { + Form { +// Section { +// Picker(selection: $eventType) { +// ForEach(EventType.allCases) { eventType in +// Text(eventType.localizedLabel()) +// } +// } label: { +// Text("Type") +// } +// } + + Section { + TextField("Nom de l'événement", text: $eventName) + } + + Section { + DatePicker(selection: $startingDate) { + Text(startingDate.formatted(.dateTime.weekday(.wide)).capitalized) + } + + if eventType == .approvedTournament { + Stepper(value: $duration, in: 1...3) { + HStack { + Text("Durée") + Spacer() + Text("\(duration) jour" + duration.pluralSuffix) + } + } + } + } header: { + Text("Démarrage") + } + + + switch eventType { + case .approvedTournament: + approvedTournamentEditorView + case .friendlyTournament: + approvedTournamentEditorView + case .simulation: + approvedTournamentEditorView + case .animation: + animationEditorView + } + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Annuler", role: .cancel) { + dismiss() + } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Valider") { + if tournaments.count > 1 || eventName.trimmed.isEmpty == false { + let event = Event(name: eventName) + tournaments.forEach { tournament in + tournament.event = event.id + } + try? dataStore.events.addOrUpdate(instance: event) + } + try? dataStore.tournaments.append(contentOfs: tournaments) + dismiss() + } + .clipShape(Capsule()) + .buttonStyle(.bordered) + } + + } + .navigationTitle("Nouvel événement") + } + } + + @ViewBuilder + private var approvedTournamentEditorView: some View { + ForEach(tournaments) { tournament in + Section { + TournamentConfigurationView(tournament: tournament) + } header: { + if tournaments.count > 1 { + HStack { + Spacer() + Button { + tournaments.removeAll(where: { $0 == tournament }) +// viewContext.delete(tournament) + } label: { + Text("effacer") + } + .textCase(nil) + } + } + } + } + + RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") { + let tournament = Tournament.newEmptyInstance() + +// let tournament = Tournament(context: viewContext) +// tournament.tournamentLevel = TournamentLevel.mostUsed(tournaments: tournaments) +// tournament.tournamentCategory = TournamentCategory.mostUsed(tournaments: tournaments).next +// tournament.federalTournamentAge = FederalTournamentAge.mostUsed(tournaments: tournaments) +// + self.tournaments.append(tournament) + } } + + @ViewBuilder + var animationEditorView: some View { + Section { + Picker(selection: $animationType) { + ForEach(AnimationType.allCases) { animationType in + Text(animationType.localizedLabel()).tag(animationType) + } + } label: { + Text("Type") + }.pickerStyle(.menu) + } + Section { + Text(animationType.descriptionLabel) + } + } + + } #Preview { EventCreationView() + .environmentObject(DataStore.shared) } diff --git a/PadelClub/Views/Event/TournamentConfiguratorView.swift b/PadelClub/Views/Event/TournamentConfiguratorView.swift new file mode 100644 index 0000000..1f1231c --- /dev/null +++ b/PadelClub/Views/Event/TournamentConfiguratorView.swift @@ -0,0 +1,51 @@ +// +// TournamentConfiguratorView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct TournamentConfigurationView: View { + @Bindable var tournament: Tournament + + var minimumTeamsCount: Int { + 4 + } + + var maximumTeamsCount: Int { + 128 + } + + @ViewBuilder + var body: some View { + Picker(selection: $tournament.federalCategory, label: Text("Catégorie")) { + ForEach(TournamentCategory.allCases) { type in + Text(type.localizedLabel()).tag(type.rawValue) + } + } + Picker(selection: $tournament.federalLevelCategory, label: Text("Niveau")) { + ForEach(TournamentLevel.allCases) { type in + Text(type.localizedLabel()).tag(type.rawValue) + } + } + Picker(selection: $tournament.federalAgeCategory, label: Text("Limite d'âge")) { + ForEach(FederalTournamentAge.allCases) { type in + Text(type.localizedLabel()).tag(type.rawValue) + } + } + + Stepper(value: $tournament.teamCount, in: minimumTeamsCount...maximumTeamsCount) { + HStack { + Text("Équipes souhaitées") + Spacer() + Text(tournament.teamCount.formatted()) + } + } + } +} +// +//#Preview { +// TournamentConfiguratorView() +//} diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 22ea31c..28f08a2 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -8,18 +8,29 @@ import SwiftUI struct ActivityView: View { + @EnvironmentObject var dataStore: DataStore + @State private var searchText: String = "" @State private var agendaDestination: AgendaDestination = .activity @State private var filterEnabled: Bool = false @State private var presentToolbar: Bool = false - + @State private var newTournament: Tournament? + + var runningTournaments: [Tournament] { + dataStore.tournaments.filter({ $0.endDate == nil }).sorted(by: \.startDate) + } + + var endedTournaments: [Tournament] { + dataStore.tournaments.filter({ $0.endDate != nil }).sorted(using: SortDescriptor(\.startDate, order: .reverse)) + } + var tournaments: [Tournament] { switch agendaDestination { case .activity: - [] + runningTournaments case .history: - DataStore.fakeTournaments + endedTournaments } } @@ -58,9 +69,7 @@ struct ActivityView: View { Text("Aucun événement n'est prévu dans votre agenda.") } actions: { RowButtonView(title: "Créer un nouvel évenement") { - let tournament = Tournament(name: "P100", club_id: "", category: 0, playerCount: 24) - try? DataStore.shared.tournaments.append(contentOfs: [tournament]) - + newTournament = Tournament.newEmptyInstance() } RowButtonView(title: "Importer vos tournois Tenup") { @@ -74,6 +83,9 @@ struct ActivityView: View { .searchable(text: $searchText) .onAppear { presentToolbar = true } .onDisappear { presentToolbar = false } + .sheet(item: $newTournament) { tournament in + EventCreationView(tournaments: [tournament]) + } .toolbar { if presentToolbar { ToolbarItem(placement: .status) { @@ -94,16 +106,23 @@ struct ActivityView: View { Button { filterEnabled.toggle() } label: { - Label("Vues", systemImage: "line.3.horizontal.decrease.circle") - .symbolVariant(filterEnabled ? .fill : .none) + Image(systemName: "line.3.horizontal.decrease.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 28) } + .symbolVariant(filterEnabled ? .fill : .none) } ToolbarItem(placement: .topBarTrailing) { Button { - + newTournament = Tournament.newEmptyInstance() + } label: { - Label("Ajouter", systemImage: "plus.circle.fill") + Image(systemName: "plus.circle.fill") + .resizable() + .scaledToFit() + .frame(minHeight: 28) } } } @@ -118,4 +137,5 @@ struct ActivityView: View { #Preview { ActivityView() + .environmentObject(DataStore.shared) } diff --git a/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift b/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift index 9a40d16..862193b 100644 --- a/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift @@ -8,7 +8,7 @@ import SwiftUI struct EmptyActivityView: View { - @State private var presentTournamentCreation: Bool = false + @State private var newTournament: Tournament? var body: some View { NavigationStack { @@ -17,7 +17,7 @@ struct EmptyActivityView: View { Section { RowButtonView(title: "Créer votre premier événement", action: { - presentTournamentCreation = true + newTournament = Tournament.newEmptyInstance() }) } @@ -27,8 +27,8 @@ struct EmptyActivityView: View { }) } } - .sheet(isPresented: $presentTournamentCreation) { - EventCreationView() + .sheet(item: $newTournament) { tournament in + EventCreationView(tournaments: [tournament]) } } } diff --git a/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift b/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift index cb5c08a..67d53d7 100644 --- a/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift +++ b/PadelClub/Views/Navigation/Organizer/TournamentOrganizerView.swift @@ -12,7 +12,7 @@ struct TournamentOrganizerView: View { var body: some View { VStack(spacing: 0) { - ForEach(DataStore.fakeTournaments) { tournament in + ForEach(DataStore.shared.tournaments) { tournament in if tournament.id == selectedTournamentId { OrganizedTournamentView(tournament: tournament) } @@ -29,7 +29,7 @@ struct TournamentOrganizerView: View { HStack { ScrollView(.horizontal) { HStack { - ForEach(DataStore.fakeTournaments) { tournament in + ForEach(DataStore.shared.tournaments) { tournament in TournamentButtonView(tournament: tournament, selectedId: $selectedTournamentId) } } diff --git a/PadelClub/Views/Shared/MatchFormatPickerView.swift b/PadelClub/Views/Shared/MatchFormatPickerView.swift new file mode 100644 index 0000000..137986d --- /dev/null +++ b/PadelClub/Views/Shared/MatchFormatPickerView.swift @@ -0,0 +1,52 @@ +// +// MatchFormatPickerView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct MatchFormatPickerView: View { + let headerLabel: String + @Binding var matchFormat: MatchFormat + @State private var isExpanded: Bool = false + + var body: some View { + DisclosureGroup(isExpanded: $isExpanded) { + Picker(selection: $matchFormat) { + ForEach(MatchFormat.allCases, id: \.rawValue) { format in + Text(format.computedShortLabel).tag(format) + } + } label: { + } + .pickerStyle(.inline) + .onChange(of: matchFormat) { + isExpanded = false + } + } label: { + descriptionView + } + } + + var descriptionView: some View { + VStack(alignment: .leading) { + HStack { + Text(headerLabel).font(.caption) + Spacer() + Text("Durée").font(.caption) + } + HStack { + Text(matchFormat.format) + Spacer() + VStack(alignment: .trailing) { + Text("~" + matchFormat.estimatedDuration.formatted() + " minutes") + Text(matchFormat.breakTime.breakTime.formatted() + " minutes de pause").foregroundStyle(.secondary) + if matchFormat.breakTime.matchCount > 1 { + Text("après \(matchFormat.breakTime.matchCount) match" + matchFormat.breakTime.matchCount.pluralSuffix).foregroundStyle(.secondary) + } + } + } + } + } +} diff --git a/PadelClub/Views/Shared/MatchTypeSelectionView.swift b/PadelClub/Views/Shared/MatchTypeSelectionView.swift new file mode 100644 index 0000000..2d3ba3c --- /dev/null +++ b/PadelClub/Views/Shared/MatchTypeSelectionView.swift @@ -0,0 +1,17 @@ +// +// MatchTypeSelectionView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct MatchTypeSelectionView: View { + @Binding var selectedFormat: MatchFormat + let format: String + + var body: some View { + MatchFormatPickerView(headerLabel: format, matchFormat: $selectedFormat) + } +} diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index 4aa9eb9..4214a34 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -73,7 +73,7 @@ struct SelectablePlayerListView: View { // Button { // searchViewModel.tokens.append(token) // } label: { - // Label(token.localizedLabel, systemImage: token.icon()) + // Label(token.localizedLabel(), systemImage: token.icon()) // } // } // }) @@ -374,7 +374,7 @@ struct MySearchView: View { } searchViewModel.sortOption = option })) { - Label(option.localizedLabel, systemImage: searchViewModel.sortOption == option ? (searchViewModel.ascending ? "chevron.up" : "chevron.down") : "") + Label(option.localizedLabel(), systemImage: searchViewModel.sortOption == option ? (searchViewModel.ascending ? "chevron.up" : "chevron.down") : "") } } } header: { @@ -400,7 +400,7 @@ struct MySearchView: View { Text("Assimilés") } } label: { - Label(searchViewModel.sortOption.localizedLabel, systemImage: searchViewModel.ascending ? "chevron.up" : "chevron.down") + Label(searchViewModel.sortOption.localizedLabel(), systemImage: searchViewModel.ascending ? "chevron.up" : "chevron.down") } } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentDatePickerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentDatePickerView.swift new file mode 100644 index 0000000..b3f8ce8 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentDatePickerView.swift @@ -0,0 +1,21 @@ +// +// TournamentDatePickerView.swift +// Padel Tournament +// +// Created by Razmig Sarkissian on 05/10/2023. +// + +import SwiftUI + +struct TournamentDatePickerView: View { + @Environment(Tournament.self) private var tournament: Tournament + + var body: some View { + @Bindable var tournament = tournament + DatePicker(selection: $tournament.startDate) { + Text(tournament.startDate.formatted(.dateTime.weekday(.wide)).capitalized) + .font(.headline) + } + .datePickerStyle(.compact) + } +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentDurationManagerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentDurationManagerView.swift new file mode 100644 index 0000000..f6e5b78 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentDurationManagerView.swift @@ -0,0 +1,29 @@ +// +// TournamentDurationManagerView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct TournamentDurationManagerView: View { + @Environment(Tournament.self) private var tournament: Tournament + + var body: some View { + @Bindable var tournament = tournament + + Stepper(value: $tournament.dayDuration, in: 1...3) { + LabeledContent { + Text("\(tournament.dayDuration) jour" + tournament.dayDuration.pluralSuffix) + } label: { + Text("Durée") + } + } + } +} + +#Preview { + TournamentDurationManagerView() + .environment(Tournament.mock()) +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift new file mode 100644 index 0000000..399af95 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentFieldsManagerView.swift @@ -0,0 +1,28 @@ +// +// TournamentFieldsManagerView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct TournamentFieldsManagerView: View { + @Environment(Tournament.self) private var tournament: Tournament + + var body: some View { + @Bindable var tournament = tournament + + Stepper(value: $tournament.courtCount, in: 1...Int.max) { + LabeledContent { + Text(tournament.courtCount.formatted()) + } label: { + Text("Nombre de terrains") + } + } + }} + +#Preview { + TournamentFieldsManagerView() + .environment(Tournament.mock()) +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentFormatSelectionView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentFormatSelectionView.swift new file mode 100644 index 0000000..74ef46c --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentFormatSelectionView.swift @@ -0,0 +1,25 @@ +// +// TournamentFormatSelectionView.swift +// Padel Tournament +// +// Created by Razmig Sarkissian on 05/10/2023. +// + +import SwiftUI + +struct TournamentFormatSelectionView: View { + @Environment(Tournament.self) private var tournament: Tournament + + @ViewBuilder + var body: some View { + @Bindable var tournament = tournament + + Section { + MatchTypeSelectionView(selectedFormat: $tournament.groupStageMatchFormat, format: "Poule") + MatchTypeSelectionView(selectedFormat: $tournament.matchFormat, format: "Tableau") + MatchTypeSelectionView(selectedFormat: $tournament.loserBracketMatchFormat, format: "Match de classement") + } footer: { + Text("À minima, les règles fédérales seront toujours prises en compte par défaut.") + } + } +} diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift new file mode 100644 index 0000000..5fa5c69 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift @@ -0,0 +1,37 @@ +// +// TournamentLevelPickerView.swift +// Padel Tournament +// +// Created by Razmig Sarkissian on 05/10/2023. +// + +import SwiftUI + +struct TournamentLevelPickerView: View { + @Environment(Tournament.self) private var tournament: Tournament + + var body: some View { + @Bindable var tournament = tournament + + Picker(selection: $tournament.tournamentCategory, label: Text("Catégorie")) { + ForEach(TournamentCategory.allCases) { type in + Text(type.localizedLabel()).tag(type) + } + } + Picker(selection: $tournament.tournamentLevel, label: Text("Niveau")) { + ForEach(TournamentLevel.allCases) { type in + Text(type.localizedLabel()).tag(type) + } + } + Picker(selection: $tournament.federalTournamentAge, label: Text("Limite d'âge")) { + ForEach(FederalTournamentAge.allCases) { type in + Text(type.localizedLabel()).tag(type) + } + } + Picker(selection: $tournament.groupStageOrderingMode, label: Text("Répartition en poule")) { + ForEach(GroupStageOrderingMode.allCases) { type in + Text(type.localizedLabel()).tag(type) + } + } + } +} diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 2dd5a8b..c75a128 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -12,7 +12,7 @@ struct InscriptionManagerView: View { var body: some View { List { - Text(tournament.playerCount.formatted()) + Text("24") } .navigationTitle("Inscriptions") } diff --git a/PadelClub/Views/Tournament/Screen/Screen.swift b/PadelClub/Views/Tournament/Screen/Screen.swift index b9dbb5a..56417d5 100644 --- a/PadelClub/Views/Tournament/Screen/Screen.swift +++ b/PadelClub/Views/Tournament/Screen/Screen.swift @@ -10,4 +10,5 @@ import Foundation enum Screen: String, Codable { case inscription case groupStage + case settings } diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift new file mode 100644 index 0000000..7225e29 --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -0,0 +1,74 @@ +// +// TournamentSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct TournamentSettingsView: View { + @Environment(Tournament.self) private var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + @State private var tournamentName: String = "" + + var body: some View { + @Bindable var tournament = tournament + Form { + LabeledContent { + TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $tournament.entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) + .keyboardType(.decimalPad) + .fixedSize() + .multilineTextAlignment(.trailing) + } label: { + Text("Inscription") + } + + LabeledContent { + TextField("Nom", text: $tournamentName) + .multilineTextAlignment(.trailing) + .fixedSize() + .keyboardType(.alphabet) + .autocorrectionDisabled() + .onSubmit { + if tournamentName.trimmed.isEmpty { + tournament.name = nil + } else { + tournament.name = tournamentName + } + } + } label: { + Text("Nom du tournoi") + } + + TournamentDurationManagerView() + + TournamentFieldsManagerView() + + TournamentDatePickerView() + TournamentFormatSelectionView() + TournamentLevelPickerView() + } + .navigationTitle("Réglages") + .toolbarBackground(.visible, for: .navigationBar) + .onAppear { + tournamentName = tournament.name ?? "" + tournament.undoManager = tournament.hashValue + } + .onDisappear { + if tournament.undoManager != tournament.hashValue { + try? dataStore.tournaments.addOrUpdate(instance: tournament) + } + } + } +} + +#Preview { + Group { + + TournamentSettingsView() + .environmentObject(DataStore.shared) + .environment(Tournament.mock()) + } +} diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 2a1145e..90832db 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -21,7 +21,7 @@ struct TournamentCellView: View { VStack(alignment: .leading, spacing: -2) { Text("Homme") .font(.caption2) - Text(tournament.name) + Text(tournament.title()) .font(.title) Text("Senior") .font(.caption2) diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift new file mode 100644 index 0000000..a9f3987 --- /dev/null +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -0,0 +1,43 @@ +// +// TournamentInitView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct TournamentInitView: View { + @Environment(Tournament.self) private var tournament: Tournament + + @ViewBuilder + var body: some View { + Section { + NavigationLink(value: Screen.settings) { + LabeledContent { + Text(tournament.settingsDescriptionLocalizedLabel()) + } label: { + Label("Réglages", systemImage: "slider.horizontal.3") + } + } + } footer: { + Text("La date, la catégorie, le niveau, le nombre de terrain, les formats, etc.") + } +// +// Section { +// NavigationLink { +// TableStructureView(tournament: tournament) +// } label: { +// Label("Structure", systemImage: "hammer") +// .badge(tournament.structureDescriptionLocalizedLabel) +// } +// } footer: { +// Text("Nombre d'équipes, de poules, de qualifiés sortant, etc.") +// } + } +} + +#Preview { + TournamentInitView() + .environment(Tournament.mock()) +} diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 22660c7..8ca0cb5 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -13,22 +13,41 @@ struct TournamentView: View { var body: some View { List { - InscriptionManagerRowView(tournament: tournament) - NavigationLink(value: Screen.groupStage) { - Text("Poules") - .badge(2) + switch tournament.state() { + case .initial: + TournamentInitView() } +// InscriptionManagerRowView(tournament: tournament) +// NavigationLink(value: Screen.groupStage) { +// Text("Poules") +// .badge(2) +// } } + .toolbarBackground(.visible, for: .navigationBar) .navigationDestination(for: Screen.self, destination: { screen in - switch screen { - case .inscription: - InscriptionManagerView(tournament: tournament) - case .groupStage: - Text("Poules \(screen.rawValue)") + Group { + switch screen { + case .settings: + TournamentSettingsView() + case .inscription: + InscriptionManagerView(tournament: tournament) + case .groupStage: + Text("Poules \(screen.rawValue)") + } } + .environment(tournament) }) - .navigationTitle(tournament.name) + .environment(tournament) + .navigationBarTitleDisplayMode(.inline) .toolbar { + ToolbarItem(placement: .principal) { + VStack { + Text(tournament.title()).font(.headline) + Text(tournament.formattedDate()) + .font(.subheadline).foregroundStyle(.secondary) + } + } + if presentationContext == .agenda { ToolbarItem(placement: .topBarTrailing) { Menu { @@ -51,8 +70,14 @@ struct TournamentView: View { var body: some View { NavigationLink(value: Screen.inscription) { Text("Inscriptions") - .badge(tournament.playerCount) + .badge(24) } } } } + +#Preview { + NavigationStack { + TournamentView(tournament: .mock(), presentationContext: .agenda) + } +}