Laurent 1 year ago
commit 92b5b91c77
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Data/Match.swift
  3. 6
      PadelClub/Data/User.swift
  4. 8
      PadelClub/Extensions/String+Extensions.swift
  5. 8
      PadelClub/Views/Club/ClubDetailView.swift
  6. 3
      PadelClub/Views/Club/ClubRowView.swift
  7. 42
      PadelClub/Views/Club/ClubsView.swift
  8. 10
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  9. 2
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  10. 2
      PadelClub/Views/Navigation/MainView.swift
  11. 28
      PadelClub/Views/Round/RoundSettingsView.swift
  12. 4
      PadelClub/Views/Round/RoundView.swift
  13. 22
      PadelClub/Views/Tournament/FileImportView.swift
  14. 19
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift

@ -1939,7 +1939,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 34;
CURRENT_PROJECT_VERSION = 37;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1977,7 +1977,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 34;
CURRENT_PROJECT_VERSION = 37;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -355,6 +355,10 @@ class Match: ModelObject, Storable {
func followingMatch() -> Match? {
guard let nextRoundId = roundObject?.nextRound()?.id else { return nil }
return getFollowingMatch(fromNextRoundId: nextRoundId)
}
func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? {
return Store.main.filter(isIncluded: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }).first
}

@ -73,8 +73,8 @@ class User: ModelObject, UserBase, Storable {
return "Sportivement,\n\(firstName) \(lastName), votre JAP."
}
func hasClubs() -> Bool {
self.clubs.isEmpty == false
func hasTenupClubs() -> Bool {
self.clubsObjects().filter({ $0.code != nil }).isEmpty == false
}
func hasFavoriteClubsAndCreatedClubs() -> Bool {
@ -90,7 +90,7 @@ class User: ModelObject, UserBase, Storable {
}
func createdClubsObjectsNotFavorite() -> [Club] {
return Store.main.filter(isIncluded: { ($0.creator == id) || clubs.contains($0.id) == false })
return Store.main.filter(isIncluded: { ($0.creator == id) && clubs.contains($0.id) == false })
}
func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) {

@ -46,16 +46,18 @@ extension String {
func acronym() -> String {
let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
if acronym.count > 10 {
return concatenateFirstLetters()
return concatenateFirstLetters().uppercased()
} else {
return acronym
return acronym.uppercased()
}
}
func concatenateFirstLetters() -> String {
// Split the input into sentences
let sentences = self.components(separatedBy: .whitespacesAndNewlines)
if sentences.count == 1 {
return String(self.prefix(10))
}
// Extract the first character of each sentence
let firstLetters = sentences.compactMap { sentence -> Character? in
let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines)

@ -17,6 +17,7 @@ struct ClubDetailView: View {
@State private var zipCode: String
@State private var selectedCourt: Court?
@Bindable var club: Club
@State private var clubDeleted: Bool = false
var displayContext: DisplayContext
var selection: ((Club) -> ())? = nil
@ -58,7 +59,7 @@ struct ClubDetailView: View {
.submitLabel( displayContext == .addition ? .next : .done)
.onSubmit(of: .text) {
if club.acronym.isEmpty {
club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
club.acronym = club.name.canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines).acronym()
}
if displayContext == .edition && city.isEmpty == true {
@ -217,9 +218,10 @@ struct ClubDetailView: View {
Section {
RowButtonView("Supprimer ce club", role: .destructive) {
do {
try dataStore.clubs.deleteById(club.id)
clubDeleted = true
dataStore.user.clubs.removeAll(where: { $0 == club.id })
self.dataStore.saveUser()
try dataStore.clubs.deleteById(club.id)
dismiss()
} catch {
Logger.error(error)
@ -240,7 +242,7 @@ struct ClubDetailView: View {
CourtView(court: court)
}
.onDisappear {
if displayContext == .edition {
if displayContext == .edition && clubDeleted == false {
do {
try dataStore.clubs.addOrUpdate(instance: club)
} catch {

@ -13,13 +13,14 @@ struct ClubRowView: View {
var body: some View {
LabeledContent {
Text(club.acronym)
// if displayContext == .edition {
// Image(systemName: club.isFavorite() ? "star.fill" : "star")
// .foregroundStyle(club.isFavorite() ? .green : .logoRed)
// }
} label: {
Text("Club")
Text(club.name)
Text(club.acronym)
}
}
}

@ -28,7 +28,13 @@ struct ClubsView: View {
var body: some View {
List {
let clubs : [Club] = dataStore.user.clubsObjects(includeCreated: false)
let onlyCreatedClubs : [Club] = dataStore.user.createdClubsObjectsNotFavorite()
if clubs.isEmpty == false || onlyCreatedClubs.isEmpty == false {
Section {
if clubs.isEmpty && onlyCreatedClubs.isEmpty == false {
_contentUnavailable()
}
ForEach(clubs) { club in
if let selection {
Button {
@ -51,8 +57,7 @@ struct ClubsView: View {
} header: {
Text("Clubs favoris")
}
let onlyCreatedClubs : [Club] = dataStore.user.createdClubsObjectsNotFavorite()
}
if onlyCreatedClubs.isEmpty == false {
Section {
@ -81,22 +86,11 @@ struct ClubsView: View {
}
}
.overlay {
if dataStore.user.hasFavoriteClubsAndCreatedClubs() == false {
ContentUnavailableView {
Label("Aucun club", systemImage: "house.and.flag.fill")
} description: {
Text("Texte décrivant l'utilité d'un club et les features que cela apporte")
} actions: {
RowButtonView("Créer un nouveau club", systemImage: "plus.circle.fill") {
newClub = Club.newEmptyInstance()
}
RowButtonView("Chercher un club", systemImage: "magnifyingglass.circle.fill") {
presentClubSearchView = true
}
if dataStore.user.clubsObjects(includeCreated: true).isEmpty {
_contentUnavailable()
}
}
}
.navigationTitle(selection == nil ? "Mes clubs" : "Choisir un club")
.navigationTitle(selection == nil ? "Clubs favoris" : "Choisir un club")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.sheet(isPresented: presentClubCreationView) {
@ -141,6 +135,22 @@ struct ClubsView: View {
}
}
}
private func _contentUnavailable() -> some View {
ContentUnavailableView {
Label("Aucun club", systemImage: "house.and.flag.fill")
} description: {
Text("Avoir un club en favori permet d'accéder aux tournois enregistrés sur Tenup.")
} actions: {
RowButtonView("Créer un nouveau club", systemImage: "plus.circle.fill") {
newClub = Club.newEmptyInstance()
}
RowButtonView("Chercher un club", systemImage: "magnifyingglass.circle.fill") {
presentClubSearchView = true
}
}
}
}
#Preview {

@ -121,14 +121,14 @@ struct ActivityView: View {
}
.task {
if navigation.agendaDestination == .tenup
&& dataStore.user.hasClubs() == true
&& dataStore.user.hasTenupClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.onChange(of: navigation.agendaDestination) {
if navigation.agendaDestination == .tenup
&& dataStore.user.hasClubs() == true
&& dataStore.user.hasTenupClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments()
}
@ -192,7 +192,7 @@ struct ActivityView: View {
.tint(.master)
}
.sheet(isPresented: $presentClubSearchView, onDismiss: {
if dataStore.user.hasClubs() == true {
if dataStore.user.hasTenupClubs() == true {
federalDataViewModel.federalTournaments.removeAll()
navigation.agendaDestination = .tenup
}
@ -247,7 +247,7 @@ struct ActivityView: View {
RowButtonView("Créer un nouvel événement") {
newTournament = Tournament.newEmptyInstance()
}
if dataStore.user.hasClubs() == false {
if dataStore.user.hasTenupClubs() == false {
RowButtonView("Chercher l'un de vos clubs") {
presentClubSearchView = true
}
@ -268,7 +268,7 @@ struct ActivityView: View {
}
private func _tenupEmptyView() -> some View {
if dataStore.user.hasClubs() == false {
if dataStore.user.hasTenupClubs() == false {
ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash")
} description: {

@ -53,7 +53,7 @@ struct EventListView: View {
.headerProminence(.increased)
.task {
if navigation.agendaDestination == .tenup
&& dataStore.user.hasClubs() == true
&& dataStore.user.hasTenupClubs() == true
&& _tournaments.isEmpty {
_gatherFederalTournaments(startDate: section)
}

@ -49,7 +49,7 @@ struct MainView: View {
}
var badgeText: Text? {
dataStore.user.username.isEmpty ? Text("!").font(.headline) : nil
Store.main.userId == nil ? Text("!").font(.headline) : nil
}
var body: some View {

@ -52,8 +52,30 @@ struct RoundSettingsView: View {
let round = Round(tournament: tournament.id, index: roundIndex, matchFormat: tournament.matchFormat)
let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
let matches = (0..<matchCount).map { //0 is final match
return Match(round: round.id, index: $0 + matchStartIndex, matchFormat: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: $0))
let nextRound = round.nextRound()
var currentIndex = 0
let matches = (0..<matchCount).map { index in //0 is final match
let match = Match(round: round.id, index: index + matchStartIndex, matchFormat: round.matchFormat)
if let nextRound, let followingMatch = match.getFollowingMatch(fromNextRoundId: nextRound.id) {
if followingMatch.disabled {
match.disabled = true
} else if index%2 == 1 && followingMatch.team(.one) != nil {
//index du match courant impair = position haut du prochain match
match.disabled = true
} else if index%2 == 0 && followingMatch.team(.two) != nil {
//index du match courant pair = position basse du prochain match
match.disabled = true
} else {
match.name = Match.setServerTitle(upperRound: round, matchIndex: currentIndex)
currentIndex += 1
}
} else {
match.name = Match.setServerTitle(upperRound: round, matchIndex: currentIndex)
currentIndex += 1
}
return match
}
do {
try dataStore.rounds.addOrUpdate(instance: round)
@ -66,6 +88,8 @@ struct RoundSettingsView: View {
Logger.error(error)
}
round.buildLoserBracket()
matches.filter { $0.disabled }.forEach { $0._toggleLoserMatchDisableState(true)
}
}
}

@ -75,7 +75,7 @@ struct RoundView: View {
Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup)
//_save()
await _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
@ -245,7 +245,7 @@ struct RoundView: View {
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") {
if isEditingTournamentSeed.wrappedValue {
if isEditingTournamentSeed.wrappedValue == true {
Task {
await _save()
}

@ -105,16 +105,6 @@ struct FileImportView: View {
}
}
if validationInProgress {
Section {
LabeledContent {
ProgressView()
} label: {
Text("Mise à jour des équipes")
}
}
}
if let errorMessage {
Section {
Text(errorMessage)
@ -157,7 +147,7 @@ struct FileImportView: View {
Section {
ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash")
}
} else if didImport && validationInProgress == false {
} else if didImport {
let _filteredTeams = filteredTeams
let previousTeams = tournament.sortedTeams()
@ -232,18 +222,24 @@ struct FileImportView: View {
}
ToolbarItem(placement: .topBarTrailing) {
if validationInProgress {
ProgressView()
} else {
ButtonValidateView {
_validate()
validationInProgress = true
}
.disabled(teams.isEmpty)
}
}
}
.interactiveDismissDisabled(validationInProgress)
.disabled(validationInProgress)
.onChange(of: validationInProgress) {
_validate()
}
}
private func _validate() {
validationInProgress = true
Task {
let previousTeams = filteredTeams.compactMap({ $0.previousTeam })

@ -152,14 +152,15 @@ struct InscriptionManagerView: View {
}
private func _handleHashDiff() async {
let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
if let teamsHash, newHash != teamsHash {
let selectedSortedTeams = tournament.selectedSortedTeams()
let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) {
self.teamsHash = newHash
if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
self.tournament.shouldVerifyBracket = true
self.tournament.shouldVerifyGroupStage = true
let waitingList = self.tournament.waitingListTeams(in: self.tournament.selectedSortedTeams())
let waitingList = self.tournament.waitingListTeams(in: selectedSortedTeams)
waitingList.forEach { team in
if team.bracketPosition != nil || team.groupStagePosition != nil {
team.resetPositions()
@ -181,10 +182,9 @@ struct InscriptionManagerView: View {
_managementView()
if _isEditingTeam() {
_buildingTeamView()
} else if tournament.unsortedTeams().isEmpty {
} else if unfilteredTeams.isEmpty {
_inscriptionTipsView()
}
if _isEditingTeam() == false {
} else {
_teamRegisteredView()
}
}
@ -284,7 +284,9 @@ struct InscriptionManagerView: View {
}
.tint(.master)
}
.sheet(isPresented: $presentImportView) {
.sheet(isPresented: $presentImportView, onDismiss: {
_getTeams()
}) {
NavigationStack {
FileImportView()
}
@ -428,12 +430,13 @@ struct InscriptionManagerView: View {
}
private func _prepareTeams() {
#if DEBUG_TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
sortedTeams = tournament.sortedTeams()
var teams = sortedTeams

Loading…
Cancel
Save