multistore
Razmig Sarkissian 2 years ago
parent 5c534fe22e
commit a7f8b1bafd
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 19
      PadelClub/Data/GroupStage.swift
  3. 16
      PadelClub/Data/MockData.swift
  4. 17
      PadelClub/Data/Tournament.swift
  5. 16
      PadelClub/Views/Cashier/CashierView.swift
  6. 6
      PadelClub/Views/GroupStage/GroupStageView.swift
  7. 15
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  8. 20
      PadelClub/Views/Round/RoundSettingsView.swift
  9. 25
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  10. 66
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  11. 14
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  12. 9
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift

@ -153,6 +153,7 @@
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; };
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; };
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; };
FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; };
FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; };
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; };
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; };
@ -444,6 +445,7 @@
FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; };
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; };
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = "<group>"; };
FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = "<group>"; };
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = "<group>"; };
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; };
FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; };
@ -1062,6 +1064,7 @@
FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */,
FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */,
FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */,
FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -1497,6 +1500,7 @@
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */,
FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */,
FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */,
FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */,
FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */,
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */,
FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */,

@ -98,7 +98,7 @@ class GroupStage: ModelObject, Storable {
if hasEnded(), let tournament = tournamentObject() {
do {
let teams = teams(true)
for (index, team) in teams {
for (index, team) in teams.enumerated() {
team.qualified = index < tournament.qualifiedPerGroupStage
if team.bracketPosition != nil && team.qualified == false {
team.bracketPosition = nil
@ -111,8 +111,8 @@ class GroupStage: ModelObject, Storable {
}
}
func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
if let scoreData = _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true) {
func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) {
let hideSetDifference = matchFormat.setsToWin == 1
let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false)))
let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: false)))
@ -123,7 +123,7 @@ class GroupStage: ModelObject, Storable {
}
}
fileprivate func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
if matches.isEmpty && nilIfEmpty { return nil }
@ -225,8 +225,9 @@ class GroupStage: ModelObject, Storable {
}
fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool
fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2))
@ -241,9 +242,11 @@ class GroupStage: ModelObject, Storable {
Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil }
}
func teams(_ sortedByScore: Bool = false) -> [TeamRegistration] {
func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
if sortedByScore {
return unsortedTeams().compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in
return unsortedTeams().compactMap({ team in
scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePosition!)
}).sorted { (lhs, rhs) in
let predicates: [TeamScoreAreInIncreasingOrder] = [
{ $0.wins < $1.wins },
{ $0.setDifference < $1.setDifference },

@ -45,22 +45,6 @@ extension Tournament {
static func mock() -> Tournament {
Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior)
}
static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let rankSourceDate = _mostRecentDateAvailable
let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed()
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge)
}
}
extension Match {

@ -1218,7 +1218,22 @@ extension Tournament: TournamentBuildHolder {
}
extension Tournament {
static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let rankSourceDate = _mostRecentDateAvailable
let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed()
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
return Tournament(creator: DataStore.shared.user?.id, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge)
}
static func fake() -> Tournament {
return Tournament(event: "Roland Garros", creator: "", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, seedCount: 8, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
}

@ -17,15 +17,15 @@ struct CashierView: View {
@State private var sortOrder: SortOrder = .ascending
@State private var searchText = ""
@State private var isSearching: Bool = false
init(event: Event) {
self.tournaments = event.tournaments
self.teams = []
}
init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament]
self.teams = teams
if teams.filter { $0.callDate != nil }.isEmpty {
_sortOption = .init(wrappedValue: .teamRank)
} else {
_sortOption = .init(wrappedValue: .callDate)
}
}
private func _sharedData() -> String {
@ -284,7 +284,9 @@ struct CashierView: View {
private func _isContentUnavailable() -> Bool {
switch sortOption {
case .teamRank, .callDate:
case .callDate:
return teams.filter({ $0.callDate != nil && _shouldDisplayTeam($0) }).isEmpty
case .teamRank:
return teams.filter({ _shouldDisplayTeam($0) }).isEmpty
default:
return teams.flatMap({ $0.players() }).filter({ _shouldDisplayPlayer($0) }).isEmpty

@ -89,12 +89,14 @@ struct GroupStageView: View {
@EnvironmentObject var dataStore: DataStore
let groupStage: GroupStage
let sortByScore: Bool
let scores: [GroupStage.TeamGroupStageScore]?
let teams: [TeamRegistration]
init(groupStage: GroupStage, sortByScore: Bool) {
self.groupStage = groupStage
self.sortByScore = sortByScore
self.teams = groupStage.teams(sortByScore)
self.scores = sortByScore ? (0..<groupStage.size).compactMap({ groupStage._score(forGroupStagePosition: $0) }) : nil
self.teams = groupStage.teams(sortByScore, scores: scores)
}
private func _teamAt(atIndex index: Int) -> TeamRegistration? {
@ -128,7 +130,7 @@ struct GroupStageView: View {
}
}
Spacer()
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) {
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition, score: scores?.first(where: { $0.team.groupStagePosition == groupStagePosition })) {
VStack(alignment: .trailing) {
HStack(spacing: 0.0) {
Text(score.wins)

@ -100,21 +100,6 @@ struct EventListView: View {
NavigationLink(value: tournament) {
TournamentCellView(tournament: tournament)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
tournament.isDeleted = true
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
// try? dataStore.tournaments.delete(instance: tournament)
} label: {
LabelDelete()
}
}
.contextMenu {
Button {

@ -14,16 +14,16 @@ struct RoundSettingsView: View {
var body: some View {
List {
Section {
RowButtonView("Enabled", role: .destructive) {
let allMatches = tournament._allMatchesIncludingDisabled()
allMatches.forEach({
$0.disabled = false
$0.byeState = false
})
try? dataStore.matches.addOrUpdate(contentOfs: allMatches)
}
}
// Section {
// RowButtonView("Enabled", role: .destructive) {
// let allMatches = tournament._allMatchesIncludingDisabled()
// allMatches.forEach({
// $0.disabled = false
// $0.byeState = false
// })
// try? dataStore.matches.addOrUpdate(contentOfs: allMatches)
// }
// }
Section {
RowButtonView("Retirer toutes les têtes de séries", role: .destructive) {
tournament.unsortedTeams().forEach({ $0.bracketPosition = nil })

@ -17,27 +17,7 @@ struct TournamentGeneralSettingsView: View {
var body: some View {
@Bindable var tournament = tournament
Form {
Section {
if tournament.hasEnded() == false {
if tournament.isCanceled == false {
RowButtonView("Annuler le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
} else {
RowButtonView("Reprendre le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
}
}
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privée")
}
} footer: {
Text("Le tournoi sera masqué sur le site padelclub.app")
}
Form {
Section {
TournamentDatePickerView()
TournamentDurationManagerView()
@ -121,9 +101,6 @@ struct TournamentGeneralSettingsView: View {
]) {
_save()
}
.onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) {
_save()
}
}
private func _save() {

@ -0,0 +1,66 @@
//
// TournamentStatusView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/05/2024.
//
import SwiftUI
import LeStorage
struct TournamentStatusView: View {
@Environment(Tournament.self) private var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
var body: some View {
@Bindable var tournament = tournament
Form {
Section {
RowButtonView("Supprimer le tournoi", role: .destructive) {
tournament.isDeleted.toggle()
}
} footer: {
Text("todo: expliquer ce que ca fait")
}
Section {
if tournament.hasEnded() == false {
if tournament.isCanceled == false {
RowButtonView("Annuler le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
} else {
RowButtonView("Reprendre le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
}
}
} footer: {
Text("todo: expliquer ce que ca fait")
}
Section {
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privée")
}
} footer: {
Text("Le tournoi sera masqué sur le site padelclub.app")
}
}
.toolbarBackground(.visible, for: .navigationBar)
.onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) {
_save()
}
}
private func _save() {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
#Preview {
TournamentStatusView()
}

@ -71,6 +71,12 @@ struct TournamentCashierView: View {
func allDestinations() -> [CashierDestination] {
var allDestinations : [CashierDestination] = []
let tournamentHasEnded = tournament.hasEnded()
if tournamentHasEnded {
allDestinations.append(.summary)
allDestinations.append(.all(tournament))
}
let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) }
allDestinations.append(contentsOf: destinations)
tournament.rounds().forEach { round in
@ -78,8 +84,12 @@ struct TournamentCashierView: View {
allDestinations.append(CashierDestination.bracket(round))
}
}
allDestinations.append(.all(tournament))
allDestinations.append(.summary)
if tournamentHasEnded == false {
allDestinations.append(.all(tournament))
allDestinations.append(.summary)
}
return allDestinations
}

@ -8,6 +8,7 @@
import SwiftUI
enum TournamentSettings: Identifiable, Selectable {
case status
case general
case club(Tournament)
case matchFormats
@ -16,8 +17,10 @@ enum TournamentSettings: Identifiable, Selectable {
func selectionLabel() -> String {
switch self {
case .status:
return "Statut"
case .matchFormats:
return "Formats de jeu"
return "Formats"
case .general:
return "Général"
case .club:
@ -48,13 +51,15 @@ struct TournamentSettingsView: View {
@Environment(Tournament.self) var tournament: Tournament
private func destinations() -> [TournamentSettings] {
[.general, .club(tournament), .matchFormats]
[.status, .general, .club(tournament), .matchFormats]
}
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: destinations(), nilDestinationIsValid: false)
switch selectedDestination! {
case .status:
TournamentStatusView()
case .matchFormats:
TournamentMatchFormatsSettingsView()
case .general:

Loading…
Cancel
Save