fix club stuff, fix cashier stuff

multistore
Razmig Sarkissian 1 year ago
parent e5e01ba5b1
commit 3e7c442aa7
  1. 2
      PadelClub/Data/Club.swift
  2. 4
      PadelClub/Data/User.swift
  3. 212
      PadelClub/Views/Cashier/CashierView.swift
  4. 101
      PadelClub/Views/Club/ClubDetailView.swift
  5. 41
      PadelClub/Views/Club/ClubsView.swift
  6. 27
      PadelClub/Views/Club/CreateClubView.swift
  7. 3
      PadelClub/Views/Club/Shared/ClubCourtSetupView.swift
  8. 8
      PadelClub/Views/Navigation/MainView.swift
  9. 2
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  10. 69
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift

@ -215,7 +215,7 @@ extension Club {
identify a club : code, name, ?? identify a club : code, name, ??
*/ */
let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || $0.code == code }) let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code })
if clubs.isEmpty == false { if clubs.isEmpty == false {
return clubs.first! return clubs.first!

@ -89,6 +89,10 @@ class User: ModelObject, UserBase, Storable {
return Store.main.filter(isIncluded: { (includeCreated && $0.creator == id) || clubs.contains($0.id) }) return Store.main.filter(isIncluded: { (includeCreated && $0.creator == id) || clubs.contains($0.id) })
} }
func createdClubsObjectsNotFavorite() -> [Club] {
return Store.main.filter(isIncluded: { ($0.creator == id) || clubs.contains($0.id) == false })
}
func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) {
if estimatedDuration == matchFormat.defaultEstimatedDuration { if estimatedDuration == matchFormat.defaultEstimatedDuration {
matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) matchFormatsDefaultDuration?.removeValue(forKey: matchFormat)

@ -11,19 +11,18 @@ import Combine
struct ShareableObject { struct ShareableObject {
let cashierViewModel: CashierViewModel let cashierViewModel: CashierViewModel
let teams: [TeamRegistration] let players: [PlayerRegistration]
let fileName: String let fileName: String
func sharedData() async -> Data? { func sharedData() async -> Data? {
let players = teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) let _players = players.filter({ cashierViewModel._shouldDisplayPlayer($0) })
.flatMap({ $0.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) })
.map { .map {
[$0.pasteData()] [$0.pasteData()]
.compacted() .compacted()
.joined(separator: "\n") .joined(separator: "\n")
} }
.joined(separator: "\n\n") .joined(separator: "\n\n")
return players.data(using: .utf8) return _players.data(using: .utf8)
} }
} }
@ -43,6 +42,16 @@ extension ShareableObject: Transferable {
} }
} }
extension Array {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, order: SortOrder) -> [Element] {
switch order {
case .ascending:
return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
case .descending:
return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
}
}
}
class CashierViewModel: ObservableObject { class CashierViewModel: ObservableObject {
let id: UUID = UUID() let id: UUID = UUID()
@ -60,10 +69,16 @@ class CashierViewModel: ObservableObject {
func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
if searchText.isEmpty == false { if searchText.isEmpty == false {
filterOption.shouldDisplayPlayer(player) && player.contains(searchText) sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) && player.contains(searchText)
} else { } else {
filterOption.shouldDisplayPlayer(player) sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player)
}
} }
func _sortPlayers(_ players: [PlayerRegistration]) -> [PlayerRegistration] {
players
.filter({ _shouldDisplayPlayer($0) })
.sorted { sortOption.compare($0, $1, order: sortOrder) }
} }
enum SortOption: Int, Identifiable, CaseIterable { enum SortOption: Int, Identifiable, CaseIterable {
@ -74,6 +89,50 @@ class CashierViewModel: ObservableObject {
case age case age
case callDate case callDate
var sortingKeyPath: AnyKeyPath {
switch self {
case .alphabeticalLastName:
return \PlayerRegistration.lastName
case .alphabeticalFirstName:
return \PlayerRegistration.firstName
case .playerRank, .teamRank, .callDate:
return \PlayerRegistration.computedRank
case .age:
return \PlayerRegistration.computedAge!
}
}
func compare(_ lhs: PlayerRegistration, _ rhs: PlayerRegistration, order: SortOrder) -> Bool {
switch self {
case .alphabeticalLastName:
return compare(lhs[keyPath: \.lastName], rhs[keyPath: \.lastName], order: order)
case .alphabeticalFirstName:
return compare(lhs[keyPath: \.firstName], rhs[keyPath: \.firstName], order: order)
case .playerRank, .teamRank, .callDate:
return compare(lhs[keyPath: \.computedRank], rhs[keyPath: \.computedRank], order: order)
case .age:
return compare(lhs[keyPath: \.computedAge!], rhs[keyPath: \.computedAge!], order: order)
}
}
private func compare<T: Comparable>(_ lhs: T, _ rhs: T, order: SortOrder) -> Bool {
switch order {
case .ascending:
return lhs < rhs
case .descending:
return lhs > rhs
}
}
func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
switch self {
case .age:
player.computedAge != nil
default:
true
}
}
var id: Int { self.rawValue } var id: Int { self.rawValue }
func localizedLabel() -> String { func localizedLabel() -> String {
switch self { switch self {
@ -131,12 +190,14 @@ struct CashierView: View {
@EnvironmentObject var cashierViewModel: CashierViewModel @EnvironmentObject var cashierViewModel: CashierViewModel
var tournaments : [Tournament] var tournaments : [Tournament]
var teams: [TeamRegistration] @State private var teams: [TeamRegistration]
@State private var players: [PlayerRegistration]
@State private var shareableObject: ShareableObject? @State private var shareableObject: ShareableObject?
init(tournament: Tournament, teams: [TeamRegistration]) { init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament] self.tournaments = [tournament]
self.teams = teams _teams = .init(wrappedValue: teams)
_players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() }))
} }
var body: some View { var body: some View {
@ -170,22 +231,21 @@ struct CashierView: View {
} }
} }
let filteredPlayers = cashierViewModel._sortPlayers(players)
if filteredPlayers.isEmpty {
_contentUnavailableView()
}
switch cashierViewModel.sortOption { switch cashierViewModel.sortOption {
case .teamRank: case .teamRank:
_byTeamRankView() TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1)
case .alphabeticalLastName: case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age:
_byPlayerLastName() PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1)
case .alphabeticalFirstName:
_byPlayerFirstName()
case .playerRank:
_byPlayerRank()
case .age:
_byPlayerAge()
case .callDate: case .callDate:
_byCallDateView() let _teams = teams.filter({ $0.callDate != nil })
TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1)
} }
} }
.searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
.onAppear { .onAppear {
cashierViewModel.searchText = "" cashierViewModel.searchText = ""
// if tournaments.count == 1 { // if tournaments.count == 1 {
@ -196,7 +256,10 @@ struct CashierView: View {
if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil { if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil {
cashierViewModel.sortOption = .teamRank cashierViewModel.sortOption = .teamRank
} }
self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, teams: teams, fileName: "Encaissement.txt") self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, players: players, fileName: "Encaissement")
}
.onChange(of: cashierViewModel.sortOrder) {
teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed()
} }
.headerProminence(.increased) .headerProminence(.increased)
.toolbar { .toolbar {
@ -211,84 +274,49 @@ struct CashierView: View {
} }
} }
@ViewBuilder struct PlayerCashierView: View {
private func _byPlayer(_ players: [PlayerRegistration]) -> some View { @EnvironmentObject var cashierViewModel: CashierViewModel
let _players = cashierViewModel.sortOrder == .ascending ? players : players.reversed() let players: [PlayerRegistration]
if _players.isEmpty { let displayTournamentTitle: Bool
_contentUnavailableView()
} else { var body: some View {
ForEach(_players) { player in ForEach(players) { player in
Section { Section {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
} header: { } header: {
HStack { if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() {
if let teamCallDate = player.team()?.callDate {
Text(teamCallDate.localizedDate())
}
Spacer()
Text(player.formattedRank())
}
} footer: {
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
Text(tournamentTitle) Text(tournamentTitle)
} }
} footer: {
if let teamCallDate = player.team()?.callDate {
Text("équipe convoqué") + Text(teamCallDate.localizedDate())
} }
} }
} }
} }
@ViewBuilder
private func _byPlayerRank() -> some View {
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.computedRank)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
} }
@ViewBuilder struct TeamRankView: View {
private func _byPlayerAge() -> some View { @EnvironmentObject var cashierViewModel: CashierViewModel
let players = teams.flatMap({ $0.unsortedPlayers() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ cashierViewModel._shouldDisplayPlayer($0) }) let teams: [TeamRegistration]
_byPlayer(players) let displayTournamentTitle: Bool
}
@ViewBuilder
private func _byPlayerLastName() -> some View {
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.lastName)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder
private func _byPlayerFirstName() -> some View {
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.firstName)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder var body: some View {
private func _byTeamRankView() -> some View { ForEach(teams) { team in
let _teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed() let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) })
let _filteredTeams = _teams.filter({ cashierViewModel._shouldDisplayTeam($0) }) if players.isEmpty == false {
if _filteredTeams.isEmpty {
_contentUnavailableView()
} else {
ForEach(_filteredTeams) { team in
Section { Section {
ForEach(team.players()) { player in ForEach(players) { player in
if cashierViewModel._shouldDisplayPlayer(player) {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
} }
}
} header: { } header: {
HStack { if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
if let callDate = team.callDate { Text(tournamentTitle)
Text(callDate.localizedDate())
}
Spacer()
VStack(alignment: .trailing, spacing: 0) {
Text("Poids").font(.caption)
Text(team.weight.formatted())
}
} }
} footer: { } footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { if let callDate = team.callDate {
Text(tournamentTitle) Text("convoqué") + Text(callDate.localizedDate())
}
} }
} }
} }
@ -296,15 +324,13 @@ struct CashierView: View {
} }
@ViewBuilder struct TeamCallDateView: View {
private func _byCallDateView() -> some View { @EnvironmentObject var cashierViewModel: CashierViewModel
let _teams = teams.filter({ $0.callDate != nil && cashierViewModel._shouldDisplayTeam($0) }) let teams: [TeamRegistration]
let displayTournamentTitle: Bool
if _teams.isEmpty {
_contentUnavailableView()
}
let groupedTeams = Dictionary(grouping: _teams) { team in var body: some View {
let groupedTeams = Dictionary(grouping: teams) { team in
team.callDate team.callDate
} }
let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed()
@ -312,16 +338,16 @@ struct CashierView: View {
ForEach(keys, id: \.self) { key in ForEach(keys, id: \.self) { key in
if let _teams = groupedTeams[key] { if let _teams = groupedTeams[key] {
ForEach(_teams) { team in ForEach(_teams) { team in
let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) })
if players.isEmpty == false {
Section { Section {
ForEach(team.players()) { player in ForEach(players) { player in
if cashierViewModel._shouldDisplayPlayer(player) {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
} }
}
} header: { } header: {
Text(key.localizedDate()) Text(key.localizedDate())
} footer: { } footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Text(tournamentTitle) Text(tournamentTitle)
} }
} }
@ -329,6 +355,8 @@ struct CashierView: View {
} }
} }
} }
}
}
private func _unavailableIcon() -> String { private func _unavailableIcon() -> String {
switch cashierViewModel.sortOption { switch cashierViewModel.sortOption {

@ -31,14 +31,43 @@ struct ClubDetailView: View {
var body: some View { var body: some View {
Form { Form {
if displayContext == .edition || displayContext == .lockedForEditing {
let isFavorite = club.isFavorite()
Section {
RowButtonView(isFavorite ? "Retirer des favoris" : "Mettre en favori", role: isFavorite ? .destructive : nil) {
if isFavorite {
dataStore.user.clubs.removeAll(where: { $0 == club.id })
} else {
dataStore.user.clubs.append(club.id)
}
self.dataStore.saveUser()
}
} footer: {
if displayContext == .lockedForEditing {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
}
}
}
Section { Section {
TextField("Nom du club", text: $club.name, axis: .vertical) TextField("Nom du club", text: $club.name)
.lineLimit(2)
.autocorrectionDisabled() .autocorrectionDisabled()
.keyboardType(.alphabet) .keyboardType(.alphabet)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.focused($focusedField, equals: ._name) .focused($focusedField, equals: ._name)
.submitLabel( displayContext == .addition ? .next : .done) .submitLabel( displayContext == .addition ? .next : .done)
.onSubmit(of: .text) {
if club.acronym.isEmpty {
club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
}
if displayContext == .edition && city.isEmpty == true {
focusedField = ._city
} else {
focusedField = nil
}
}
LabeledContent { LabeledContent {
if acronymMode == .automatic || displayContext == .lockedForEditing { if acronymMode == .automatic || displayContext == .lockedForEditing {
Text(club.acronym) Text(club.acronym)
@ -85,9 +114,7 @@ struct ClubDetailView: View {
} }
} }
} footer: { } footer: {
if displayContext == .lockedForEditing { if displayContext != .lockedForEditing {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
} else {
Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut.") Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut.")
} }
} }
@ -132,26 +159,15 @@ struct ClubDetailView: View {
.onTapGesture { .onTapGesture {
focusedField = ._zipCode focusedField = ._zipCode
} }
} footer: {
if displayContext == .lockedForEditing {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
}
} }
.disabled(displayContext == .lockedForEditing) .disabled(displayContext == .lockedForEditing)
} }
ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt) let federalLink = club.federalLink()
let padelClubLink = club.shareURL()
if let padelClubLink = club.shareURL() { if federalLink != nil || padelClubLink != nil {
Section {
Link(destination: padelClubLink) {
Text("Accéder au club sur le site Padel Club")
}
}
}
if let federalLink = club.federalLink() {
Section { Section {
if let federalLink {
LabeledContent("Code Club") { LabeledContent("Code Club") {
Text(club.code ?? "") Text(club.code ?? "")
} }
@ -159,7 +175,13 @@ struct ClubDetailView: View {
Text(club.city ?? "") Text(club.city ?? "")
} }
Link(destination: federalLink) { Link(destination: federalLink) {
Text("Voir la fiche du club sur tenup") Text("Accéder au club sur Tenup")
}
}
if let padelClubLink {
Link(destination: padelClubLink) {
Text("Accéder au club sur Padel Club")
}
} }
} }
} }
@ -189,20 +211,7 @@ struct ClubDetailView: View {
} }
} }
if displayContext == .edition || displayContext == .lockedForEditing { ClubCourtSetupView(club: club, displayContext: displayContext, selectedCourt: $selectedCourt, hideLockForEditingMessage: true)
let isFavorite = club.isFavorite()
Section {
RowButtonView(isFavorite ? "Retirer des favoris" : "Mettre en favori", role: isFavorite ? .destructive : nil) {
if isFavorite {
dataStore.user.clubs.removeAll(where: { $0 == club.id })
} else {
dataStore.user.clubs.append(club.id)
}
self.dataStore.saveUser()
}
}
}
if displayContext == .edition { if displayContext == .edition {
Section { Section {
@ -211,6 +220,7 @@ struct ClubDetailView: View {
try dataStore.clubs.deleteById(club.id) try dataStore.clubs.deleteById(club.id)
dataStore.user.clubs.removeAll(where: { $0 == club.id }) dataStore.user.clubs.removeAll(where: { $0 == club.id })
self.dataStore.saveUser() self.dataStore.saveUser()
dismiss()
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -245,27 +255,6 @@ struct ClubDetailView: View {
} }
} }
} }
.toolbar {
if focusedField == ._name {
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button("Valider") {
if club.acronym.isEmpty {
club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
}
if displayContext == .edition && city.isEmpty == true {
focusedField = ._city
} else {
focusedField = nil
}
}
.buttonStyle(.bordered)
}
}
}
}
} }
} }

@ -27,7 +27,8 @@ struct ClubsView: View {
var body: some View { var body: some View {
List { List {
let clubs : [Club] = dataStore.user.clubsObjects(includeCreated: true) let clubs : [Club] = dataStore.user.clubsObjects(includeCreated: false)
Section {
ForEach(clubs) { club in ForEach(clubs) { club in
if let selection { if let selection {
Button { Button {
@ -45,13 +46,37 @@ struct ClubsView: View {
} label: { } label: {
ClubRowView(club: club) ClubRowView(club: club)
} }
// .swipeActions(edge: .trailing, allowsFullSwipe: true) { }
// Button(role: .destructive) { }
// try? dataStore.clubs.delete(instance: club) } header: {
// } label: { Text("Clubs favoris")
// LabelDelete() }
// }
// } let onlyCreatedClubs : [Club] = dataStore.user.createdClubsObjectsNotFavorite()
if onlyCreatedClubs.isEmpty == false {
Section {
ForEach(onlyCreatedClubs) { club in
if let selection {
Button {
selection(club)
dismiss()
} label: {
ClubRowView(club: club, displayContext: .selection)
.frame(maxWidth: .infinity)
}
.contentShape(Rectangle())
.buttonStyle(.plain)
} else {
NavigationLink {
ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user.id) ? .edition : .lockedForEditing)
} label: {
ClubRowView(club: club)
}
}
}
} header: {
Text("Clubs créés retirés des favoris")
} }
} }
} }

@ -14,16 +14,11 @@ struct CreateClubView: View {
var club: Club var club: Club
var selection: ((Club) -> ())? = nil var selection: ((Club) -> ())? = nil
@State private var validationInProgress: Bool = false
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ClubDetailView(club: club, displayContext: .addition, selection: selection) ClubDetailView(club: club, displayContext: .addition, selection: selection)
.task {
do {
try await dataStore.clubs.loadDataFromServerIfAllowed()
} catch {
Logger.error(error)
}
}
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) { Button("Annuler", role: .cancel) {
@ -31,7 +26,24 @@ struct CreateClubView: View {
} }
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
if validationInProgress {
ProgressView()
} else {
ButtonValidateView { ButtonValidateView {
validationInProgress = true
}
.disabled(club.isValid == false)
}
}
}
.onChange(of: validationInProgress) {
if validationInProgress {
Task {
do {
try await dataStore.clubs.loadDataFromServerIfAllowed()
} catch {
Logger.error(error)
}
let existingOrCreatedClub = Club.findOrCreate(name: club.name, code: club.code, city: club.city, zipCode: club.zipCode) let existingOrCreatedClub = Club.findOrCreate(name: club.name, code: club.code, city: club.city, zipCode: club.zipCode)
@ -53,7 +65,6 @@ struct CreateClubView: View {
dismiss() dismiss()
selection?(existingOrCreatedClub) selection?(existingOrCreatedClub)
} }
.disabled(club.isValid == false)
} }
} }
} }

@ -13,6 +13,7 @@ struct ClubCourtSetupView: View {
@Bindable var club: Club @Bindable var club: Club
let displayContext: DisplayContext let displayContext: DisplayContext
@Binding var selectedCourt: Court? @Binding var selectedCourt: Court?
var hideLockForEditingMessage: Bool = false
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
@ -41,7 +42,7 @@ struct ClubCourtSetupView: View {
// } // }
// } // }
} footer: { } footer: {
if displayContext == .lockedForEditing { if displayContext == .lockedForEditing && hideLockForEditingMessage == false {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
} }
} }

@ -48,7 +48,9 @@ struct MainView: View {
dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }) dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil })
} }
var badgeText: Text? = Store.main.userId == nil ? Text("!").font(.headline) : nil var badgeText: Text? {
dataStore.user.username.isEmpty ? Text("!").font(.headline) : nil
}
var body: some View { var body: some View {
TabView(selection: selectedTabHandler) { TabView(selection: selectedTabHandler) {
@ -67,8 +69,8 @@ struct MainView: View {
// PadelClubView() // PadelClubView()
// .tabItem(for: .padelClub) // .tabItem(for: .padelClub)
} }
.id(Store.main.userId) .id(dataStore.user.id)
.onChange(of: Store.main.userId) { .onChange(of: dataStore.user.id) {
navigation.path.removeLast(navigation.path.count) navigation.path.removeLast(navigation.path.count)
} }
.environmentObject(dataStore) .environmentObject(dataStore)

@ -109,7 +109,7 @@ struct UmpireView: View {
LabeledContent { LabeledContent {
Text(dataStore.user.clubs.count.formatted()) Text(dataStore.user.clubs.count.formatted())
} label: { } label: {
Label("Mes clubs", systemImage: "house.and.flag") Label("Clubs favoris", systemImage: "house.and.flag")
} }
} }
} footer: { } footer: {

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import Combine
enum CashierDestination: Identifiable, Selectable, Equatable { enum CashierDestination: Identifiable, Selectable, Equatable {
@ -20,8 +21,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
var id: String { var id: String {
switch self { switch self {
case .summary, .all: case .summary:
return String(describing: self) return String(describing: self)
case .all(let tournament):
return tournament.id
case .groupStage(let groupStage): case .groupStage(let groupStage):
return groupStage.id return groupStage.id
case .bracket(let round): case .bracket(let round):
@ -80,15 +83,19 @@ struct TournamentCashierView: View {
var tournament: Tournament var tournament: Tournament
@State private var selectedDestination: CashierDestination? @State private var selectedDestination: CashierDestination?
@StateObject private var cashierViewModel: CashierViewModel = CashierViewModel() @StateObject private var cashierViewModel: CashierViewModel = CashierViewModel()
var allDestinations: [CashierDestination]
init(tournament: Tournament) {
self.tournament = tournament
func allDestinations() -> [CashierDestination] {
var allDestinations : [CashierDestination] = [] var allDestinations : [CashierDestination] = []
let tournamentHasEnded = tournament.hasEnded() let tournamentHasEnded = tournament.hasEnded()
if tournamentHasEnded { if tournamentHasEnded {
allDestinations.append(.summary) allDestinations.append(.summary)
} }
allDestinations.append(.all(tournament)) let all = CashierDestination.all(tournament)
allDestinations.append(all)
let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) }
allDestinations.append(contentsOf: destinations) allDestinations.append(contentsOf: destinations)
@ -102,50 +109,49 @@ struct TournamentCashierView: View {
allDestinations.append(.summary) allDestinations.append(.summary)
} }
return allDestinations self.allDestinations = allDestinations
}
init(tournament: Tournament) {
self.tournament = tournament
if tournament.hasEnded() { if tournament.hasEnded() {
if tournament.players().anySatisfy({ $0.hasPaid() == false }) == false { if tournament.players().anySatisfy({ $0.hasPaid() == false }) == false {
_selectedDestination = .init(wrappedValue: .summary) _selectedDestination = .init(wrappedValue: .summary)
} else { } else {
_selectedDestination = .init(wrappedValue: .all(tournament)) _selectedDestination = .init(wrappedValue: all)
} }
} else { } else {
let gs = tournament.getActiveGroupStage() let gs = tournament.getActiveGroupStage()
if let gs { if let gs, let destination = allDestinations.first(where: { $0.id == gs.id }) {
_selectedDestination = State(wrappedValue: .groupStage(gs)) _selectedDestination = State(wrappedValue: destination)
} else if let rs = tournament.getActiveRound(withSeeds: true) { } else if let rs = tournament.getActiveRound(withSeeds: true), let destination = allDestinations.first(where: { $0.id == rs.id }) {
_selectedDestination = State(wrappedValue: .bracket(rs)) _selectedDestination = State(wrappedValue: destination)
} }
} }
} }
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations, nilDestinationIsValid: true)
switch selectedDestination { switch selectedDestination {
case .none: case .none:
CashierSettingsView(tournament: tournament) CashierSettingsView(tournament: tournament)
case .some(let selectedCall): case .some(let selectedCall):
switch selectedCall { switch selectedCall {
case .summary: case .summary:
CashierDetailView(tournament: tournament) CashierDetailView(tournament: tournament).id(selectedCall.id)
case .groupStage(let groupStage): case .groupStage(let groupStage):
CashierView(tournament: tournament, teams: groupStage.teams()) CashierView(tournament: tournament, teams: groupStage.teams()).id(selectedCall.id)
.environmentObject(cashierViewModel) .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
case .bracket(let round): case .bracket(let round):
CashierView(tournament: tournament, teams: round.seeds()) CashierView(tournament: tournament, teams: round.seeds()).id(selectedCall.id)
.environmentObject(cashierViewModel) .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
case .all(let tournament): case .all(let tournament):
CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()) CashierView(tournament: tournament, teams: tournament.selectedSortedTeams()).id(selectedCall.id)
.environmentObject(cashierViewModel) .searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
} }
} }
} }
.environment(tournament) .environment(tournament)
.environmentObject(cashierViewModel)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Encaissement") .navigationTitle("Encaissement")
@ -155,3 +161,26 @@ struct TournamentCashierView: View {
#Preview { #Preview {
TournamentCashierView(tournament: Tournament.mock()) TournamentCashierView(tournament: Tournament.mock())
} }
//class DebouncedObject: ObservableObject {
// @Published var searchText: String = "" {
// didSet {
// debounceSearchTextPublisher.send(searchText)
// }
// }
//
// private var debounceSearchTextPublisher = PassthroughSubject<String, Never>()
// private var cancellables = Set<AnyCancellable>()
//
// init() {
// debounceSearchTextPublisher
// .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
// .sink { [weak self] in self?.performSearch(with: $0) }
// .store(in: &cancellables)
// }
//
// private func performSearch(with text: String) {
// // Perform the search with the debounced text
// print("Performing search with text: \(text)")
// }
//}

Loading…
Cancel
Save