clean up new features

xcode16
Razmig Sarkissian 1 year ago
parent 470aa619d6
commit 68338f580a
  1. 2
      PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift
  2. 7
      PadelClub/Data/User.swift
  3. 16
      PadelClub/Extensions/Array+Extensions.swift
  4. 20
      PadelClub/Extensions/Calendar+Extensions.swift
  5. 8
      PadelClub/ViewModel/FederalDataViewModel.swift
  6. 33
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  7. 11
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  8. 36
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  9. 6
      PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift
  10. 54
      PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift

@ -59,7 +59,7 @@ extension ImportedPlayer: PlayerHolder {
} }
func pasteData() -> String { func pasteData() -> String {
return [firstName?.capitalized, lastName?.capitalized, license].compactMap({ $0 }).joined(separator: " ") return [firstName?.capitalized, lastName?.capitalized, license?.computedLicense].compactMap({ $0 }).joined(separator: " ")
} }
func isNotFromCurrentDate() -> Bool { func isNotFromCurrentDate() -> Bool {

@ -75,6 +75,13 @@ class User: ModelObject, UserBase, Storable {
return "Sportivement,\n\(firstName) \(lastName), votre JAP." return "Sportivement,\n\(firstName) \(lastName), votre JAP."
} }
func fullName() -> String? {
guard firstName.isEmpty == false && lastName.isEmpty == false else {
return nil
}
return "\(firstName) \(lastName)"
}
func hasTenupClubs() -> Bool { func hasTenupClubs() -> Bool {
self.clubsObjects().filter({ $0.code != nil }).isEmpty == false self.clubsObjects().filter({ $0.code != nil }).isEmpty == false
} }

@ -78,3 +78,19 @@ extension Dictionary where Key == Int, Value == [String] {
} }
} }
} }
extension Array where Element == String {
func formatList(maxDisplay: Int = 2) -> [String] {
// Check if the array has fewer or equal elements than the maximum display limit
if self.count <= maxDisplay {
// Join all elements with commas
return self
} else {
// Join only the first `maxDisplay` elements and add "et plus"
let displayedItems = self.prefix(maxDisplay)
let remainingCount = self.count - maxDisplay
return displayedItems.dropLast() + [displayedItems.last! + " et \(remainingCount) de plus"]
}
}
}

@ -49,3 +49,23 @@ extension Calendar {
return sportYear return sportYear
} }
} }
extension Calendar {
// Add or subtract months from a date
func addMonths(_ months: Int, to date: Date) -> Date {
return self.date(byAdding: .month, value: months, to: date)!
}
// Generate a list of month start dates between two dates
func generateMonthRange(startDate: Date, endDate: Date) -> [Date] {
var dates: [Date] = []
var currentDate = startDate
while currentDate <= endDate {
dates.append(currentDate)
currentDate = self.addMonths(1, to: currentDate)
}
return dates
}
}

@ -25,15 +25,15 @@ class FederalDataViewModel {
func filterStatus() -> String { func filterStatus() -> String {
var labels: [String] = [] var labels: [String] = []
labels.append(contentsOf: levels.map { $0.localizedLabel() }) labels.append(contentsOf: levels.map { $0.localizedLabel() }.formatList())
labels.append(contentsOf: categories.map { $0.localizedLabel() }) labels.append(contentsOf: categories.map { $0.localizedLabel() }.formatList())
labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }.formatList())
let clubNames = selectedClubs.compactMap { codeClub in let clubNames = selectedClubs.compactMap { codeClub in
let club: Club? = DataStore.shared.clubs.first(where: { $0.code == codeClub }) let club: Club? = DataStore.shared.clubs.first(where: { $0.code == codeClub })
return club?.clubTitle(.short) return club?.clubTitle(.short)
} }
labels.append(contentsOf: clubNames) labels.append(contentsOf: clubNames.formatList())
if dayPeriod != .all { if dayPeriod != .all {
labels.append(dayPeriod.localizedDayPeriodLabel()) labels.append(dayPeriod.localizedDayPeriodLabel())
} }

@ -90,17 +90,24 @@ struct ActivityView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false)
List { ScrollViewReader { proxy in
switch navigation.agendaDestination! { List {
case .activity: switch navigation.agendaDestination! {
EventListView(tournaments: runningTournaments, sortAscending: true) case .activity:
case .history: EventListView(tournaments: runningTournaments, sortAscending: true)
EventListView(tournaments: endedTournaments, sortAscending: false) case .history:
case .tenup: EventListView(tournaments: endedTournaments, sortAscending: false)
EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) case .tenup:
.id(uuid) EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true)
case .around: .id(uuid)
EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) case .around:
EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true)
}
}
.onChange(of: navigation.agendaDestination) {
withAnimation {
proxy.scrollTo(0, anchor: .center)
}
} }
} }
.environment(\.viewStyle, viewStyle) .environment(\.viewStyle, viewStyle)
@ -227,7 +234,7 @@ struct ActivityView: View {
} }
} }
if presentToolbar, tournaments.isEmpty == false { if presentToolbar, tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around {
ToolbarItemGroup(placement: .bottomBar) { ToolbarItemGroup(placement: .bottomBar) {
VStack(spacing: 0) { VStack(spacing: 0) {
let searchStatus = _searchStatus() let searchStatus = _searchStatus()
@ -447,7 +454,7 @@ struct ActivityView: View {
ContentUnavailableView { ContentUnavailableView {
Label("Recherche de tournoi", systemImage: "magnifyingglass") Label("Recherche de tournoi", systemImage: "magnifyingglass")
} description: { } description: {
Text("Chercher les tournois autour de vous pour vous aidez à mieux selectionner ce que vous pouvez proposer.") Text("Chercher les tournois autour de vous pour mieux décider les tournois à proposer dans votre club. Padel Club vous facilite même l'inscription !")
} actions: { } actions: {
RowButtonView("Lancer la recherche") { RowButtonView("Lancer la recherche") {
displaySearchView = true displaySearchView = true

@ -91,8 +91,15 @@ struct CalendarView: View {
Menu { Menu {
ForEach(tournament.tournaments, id: \.id) { build in ForEach(tournament.tournaments, id: \.id) { build in
if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) {
Button(build.buildHolderTitle()) {
_createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) if navigation.agendaDestination == .around {
NavigationLink(build.buildHolderTitle()) {
TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user)
}
} else {
Button(build.buildHolderTitle()) {
_createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build)
}
} }
} }
} }

@ -21,7 +21,10 @@ struct EventListView: View {
let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth } let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth }
switch viewStyle { switch viewStyle {
case .list: case .list:
ForEach(groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }), id: \.self) { section in let nextMonths = groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 })
ForEach(nextMonths.indices, id: \.self) { sectionIndex in
let section = nextMonths[sectionIndex]
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: sortAscending ? { $0.startDate < $1.startDate } : { $0.startDate > $1.startDate } if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: sortAscending ? { $0.startDate < $1.startDate } : { $0.startDate > $1.startDate }
) { ) {
Section { Section {
@ -34,11 +37,14 @@ struct EventListView: View {
Text("\(count.formatted()) tournoi" + count.pluralSuffix) Text("\(count.formatted()) tournoi" + count.pluralSuffix)
} }
} }
.id(sectionIndex)
.headerProminence(.increased) .headerProminence(.increased)
} }
} }
case .calendar: case .calendar:
ForEach(_nextMonths(), id: \.self) { section in let nextMonths = _nextMonths()
ForEach(nextMonths.indices, id: \.self) { sectionIndex in
let section = nextMonths[sectionIndex]
let _tournaments = groupedTournamentsByDate[section] ?? [] let _tournaments = groupedTournamentsByDate[section] ?? []
Section { Section {
CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id)
@ -50,6 +56,7 @@ struct EventListView: View {
Text("\(count.formatted()) tournoi" + count.pluralSuffix) Text("\(count.formatted()) tournoi" + count.pluralSuffix)
} }
} }
.id(sectionIndex)
.headerProminence(.increased) .headerProminence(.increased)
.task { .task {
if navigation.agendaDestination == .tenup if navigation.agendaDestination == .tenup
@ -77,16 +84,25 @@ struct EventListView: View {
} }
private func _nextMonths() -> [Date] { private func _nextMonths() -> [Date] {
var result: [Date] = [] let currentDate = Date().startOfMonth
var currentDate = Date().startOfMonth let uniqueDates = tournaments.map { $0.startDate.startOfMonth }.uniqued().sorted()
let firstMonthOfDate = uniqueDates.first
let lastMonthOfDate = uniqueDates.last
let calendar = Calendar.current
// Generate 100 future months if let firstMonthOfDate, let lastMonthOfDate {
for _ in 0..<12 { if navigation.agendaDestination == .history {
result.append(currentDate) return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate).reversed()
currentDate = Calendar.current.date(byAdding: .month, value: 1, to: currentDate)! } else if navigation.agendaDestination == .around || navigation.agendaDestination == .tenup {
return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate)
} else {
let min = min(currentDate, firstMonthOfDate)
let max = max(currentDate, lastMonthOfDate)
return calendar.generateMonthRange(startDate: min, endDate: calendar.addMonths(3, to: max))
}
} else {
return calendar.generateMonthRange(startDate: currentDate, endDate: calendar.addMonths(3, to: currentDate))
} }
return result
} }
private func _listView(_ tournaments: [FederalTournamentHolder]) -> some View { private func _listView(_ tournaments: [FederalTournamentHolder]) -> some View {

@ -27,10 +27,10 @@ struct TournamentLookUpView: View {
@State private var endDate: Date = Calendar.current.date(byAdding: .month, value: 3, to: Date())! @State private var endDate: Date = Calendar.current.date(byAdding: .month, value: 3, to: Date())!
@AppStorage("lastCity") private var city: String = "" @AppStorage("lastCity") private var city: String = ""
@State private var ligue: String = "" @State private var ligue: String = ""
@AppStorage("lastDistance") private var distance: Double = 30 @State private var distance: Double = 30
@AppStorage("lastSortingOption") private var sortingOption: String = "dateDebut+asc" @State private var sortingOption: String = "dateDebut+asc"
@State private var requestedToGetAllPages: Bool = false @State private var requestedToGetAllPages: Bool = false
@AppStorage("lastNationalCup") private var nationalCup: Bool = false @State private var nationalCup: Bool = false
@State private var revealSearchParameters: Bool = true @State private var revealSearchParameters: Bool = true
@State private var presentAlert: Bool = false @State private var presentAlert: Bool = false

@ -12,6 +12,7 @@ struct TournamentSubscriptionView: View {
let federalTournament: FederalTournament let federalTournament: FederalTournament
let build: any TournamentBuildHolder let build: any TournamentBuildHolder
let user: User
@State private var selectedPlayers: [ImportedPlayer] @State private var selectedPlayers: [ImportedPlayer]
@State private var contactType: ContactType? = nil @State private var contactType: ContactType? = nil
@ -20,6 +21,7 @@ struct TournamentSubscriptionView: View {
init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) {
self.federalTournament = federalTournament self.federalTournament = federalTournament
self.build = build self.build = build
self.user = user
_selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 }))
} }
@ -84,36 +86,43 @@ struct TournamentSubscriptionView: View {
} }
} }
let teams = selectedPlayers.map { $0.pasteData() }.joined(separator: "\n")
let body = [[build.buildHolderTitle(), federalTournament.computedStartDate].compacted().joined(separator: " "), teams].compactMap { $0 }.joined(separator: "\n") + "\n"
let subject = [build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " ")
if let courrielEngagement = federalTournament.courrielEngagement { if let courrielEngagement = federalTournament.courrielEngagement {
Section { Section {
RowButtonView("Contacter par email") { RowButtonView("Contacter par email") {
contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: body, subject: subject, tournamentBuild: build as? TournamentBuild) contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild)
} }
} }
} }
if let installation = federalTournament.installation, let telephone = installation.telephone { if let installation = federalTournament.installation, let telephone = installation.telephone {
if telephone.isMobileNumber() { if telephone.isMobileNumber() {
let body = [[build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " "), federalTournament.computedStartDate, teams].compacted().joined(separator: "\n") + "\n"
Section { Section {
RowButtonView("Contacter par message") { RowButtonView("Contacter par message") {
contactType = .message(date: nil, recipients: [telephone], body: body, tournamentBuild: build as? TournamentBuild) contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
} }
} }
} else { }
let number = telephone.replacingOccurrences(of: " ", with: "") let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") { if let url = URL(string: "tel:\(number)") {
Link(destination: url) { Link(destination: url) {
Label("Appeler", systemImage: "phone") Label("Appeler", systemImage: "phone")
}
} }
} }
} }
} }
.toolbar(content: {
Menu {
Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(federalTournament.id)")!) {
Label("Voir sur Tenup", systemImage: "tennisball")
}
ShareLink(item: federalTournament.shareMessage) {
Label("Partager les infos", systemImage: "info")
}
} label: {
LabelOptions()
}
})
.alert("Un problème est survenu", isPresented: messageSentFailed) { .alert("Un problème est survenu", isPresented: messageSentFailed) {
Button("OK") { Button("OK") {
} }
@ -164,6 +173,27 @@ struct TournamentSubscriptionView: View {
.navigationTitle("Détail du tournoi") .navigationTitle("Détail du tournoi")
} }
var teamsString: String {
selectedPlayers.map { $0.pasteData() }.joined(separator: "\n")
}
var messageBody: String {
let bonjourOuBonsoir = Date().timeOfDay.hello
let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye
let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\nVotre tournoi n'est pas encore dessus ? \(URLs.main.rawValue)", "Téléchargez l'app : \(URLs.appStore.rawValue)", "En savoir plus : \(URLs.appDescription.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n"
return body
}
var messageBodyShort: String {
let body = [[build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " "), federalTournament.computedStartDate, teamsString].compacted().joined(separator: "\n") + "\n"
return body
}
var messageSubject: String {
let subject = [build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " ")
return subject
}
var messageSentFailed: Binding<Bool> { var messageSentFailed: Binding<Bool> {
Binding { Binding {
sentError != nil sentError != nil

Loading…
Cancel
Save