parent
fd661687c3
commit
c0fb636621
@ -0,0 +1,28 @@ |
||||
// |
||||
// ClubHolder.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 13/11/2023. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
protocol ClubHolder { |
||||
var clubName: String? { get } |
||||
} |
||||
|
||||
extension ClubHolder { |
||||
var shortSmartClubName: String? { |
||||
var clubName = clubName |
||||
clubName = clubName?.replacingOccurrences(of: "TENNIS SPORTING CLUB", with: "TSC") |
||||
clubName = clubName?.replacingOccurrences(of: "SPORTING CLUB TENNIS", with: "SCT") |
||||
clubName = clubName?.replacingOccurrences(of: "TENNIS CLUB SPORTING", with: "TCS") |
||||
clubName = clubName?.replacingOccurrences(of: "SPORTING CLUB", with: "SC") |
||||
clubName = clubName?.replacingOccurrences(of: "SPORTING ", with: "") |
||||
clubName = clubName?.replacingOccurrences(of: "TENNIS CLUB", with: "TC") |
||||
clubName = clubName?.replaceCharactersFromSet(characterSet: .punctuationCharacters) |
||||
return clubName |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,585 @@ |
||||
// This file was generated from JSON Schema using quicktype, do not modify it directly. |
||||
// To parse the JSON, add this file to your project and do: |
||||
// |
||||
// let federalTournament = try? JSONDecoder().decode(FederalTournament.self, from: jsonData) |
||||
|
||||
import Foundation |
||||
import CoreLocation |
||||
|
||||
enum DayPeriod { |
||||
case all |
||||
case weekend |
||||
case week |
||||
} |
||||
|
||||
// MARK: - FederalTournament |
||||
struct FederalTournament: Identifiable, Codable { |
||||
|
||||
func getEvent() -> Event { |
||||
var club = DataStore.shared.clubs.first(where: { $0.code == codeClub }) |
||||
if club == nil { |
||||
club = Club(name: clubLabel(), code: codeClub) |
||||
try? DataStore.shared.clubs.addOrUpdate(instance: club!) |
||||
} |
||||
|
||||
var event = DataStore.shared.events.first(where: { $0.tenupId == id.string }) |
||||
if event == nil { |
||||
event = Event(club: club?.id, name: libelle, tenupId: id.string) |
||||
try? DataStore.shared.events.addOrUpdate(instance: event!) |
||||
} |
||||
return event! |
||||
} |
||||
|
||||
static func sectionedData(from federalTournaments: [FederalTournament]) -> [String: [FederalTournament]] { |
||||
Dictionary(grouping: federalTournaments) { federalTournament in |
||||
URL.importDateFormatter.string(from: (federalTournament.dateDebut ?? .distantFuture)) |
||||
} |
||||
} |
||||
|
||||
|
||||
let id: Int |
||||
var millesime: Int? |
||||
var libelle: String? |
||||
var tmc: Bool? |
||||
var tarifAdulteChampionnat: Double? |
||||
var type: String? |
||||
var ageReel: Bool? |
||||
var naturesTerrains: [JSONAny]? |
||||
var idsArbitres: [JSONAny]? |
||||
var tarifJeuneChampionnat: Double? |
||||
var international, inscriptionEnLigne: Bool? |
||||
var categorieTournoi: CategorieTournoi? |
||||
var prixLot: Int? |
||||
var paiementEnLigne: Bool? |
||||
var reductionAdherentJeune, reductionAdherentAdulte: Double? |
||||
var paiementEnLigneObligatoire: Bool? |
||||
var villeEngagement: String? |
||||
var senior, veteran, inscriptionEnLigneEnCours, avecResultatPublie: Bool? |
||||
var code: String? |
||||
var categorieAge: CategorieAge? |
||||
var codeComite: String? |
||||
var installations: [JSONAny]? |
||||
var reductionEpreuveSupplementaireJeune, reductionEpreuveSupplementaireAdulte: Double? |
||||
var nomComite: String? |
||||
var naturesEpreuves: [Serie]? |
||||
var jeune: Bool? |
||||
var courrielEngagement, nomClub: String? |
||||
var installation: Installation? |
||||
var categorieAgeMax: CategorieAge? |
||||
var tournoiInterne: Bool? |
||||
var nomLigue, nomEngagement, codeLigue: String? |
||||
var modeleDeBalle: ModeleDeBalle? |
||||
var jugeArbitre: JugeArbitre? |
||||
var adresse2Engagement: String? |
||||
var epreuves: [Epreuve]? |
||||
var dateDebut: Date? |
||||
var serie: Serie? |
||||
var dateFin, dateValidation: Date? |
||||
var codePostalEngagement, codeClub: String? |
||||
var prixEspece: Int? |
||||
var distanceEnMetres: Double? |
||||
|
||||
var dayPeriod: DayPeriod { |
||||
if let dateDebut { |
||||
let day = dateDebut.get(.weekday) |
||||
switch day { |
||||
case 2...6: |
||||
return .week |
||||
default: |
||||
return .weekend |
||||
} |
||||
} |
||||
return .all |
||||
} |
||||
|
||||
var dayDuration: Int { |
||||
if let dateDebut, let dateFin { |
||||
return Calendar.current.numberOfDaysBetween(dateDebut, and: dateFin) + 1 |
||||
} else { |
||||
return 1 |
||||
} |
||||
} |
||||
|
||||
var city: String? { |
||||
if let installation { return installation.ville } |
||||
return nil |
||||
} |
||||
|
||||
var location: CLLocation? { |
||||
if let lat = installation?.lat, let lng = installation?.lng { |
||||
return CLLocation(latitude: lat, longitude: lng) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
var computedStartDate: String? { |
||||
dateDebut?.formatted(.dateTime.day(.defaultDigits).month(.abbreviated).year()) |
||||
} |
||||
|
||||
var tournaments: [any TournamentBuildHolder] { |
||||
epreuves? |
||||
.compactMap({ $0.tournamentBuild }) |
||||
.sorted(using: |
||||
.keyPath(\TournamentBuild.level.order), |
||||
.keyPath(\TournamentBuild.category.order), |
||||
.keyPath(\TournamentBuild.age.order)) |
||||
?? [] |
||||
} |
||||
|
||||
var shareMessage: String { |
||||
[libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n" |
||||
} |
||||
|
||||
func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool { |
||||
var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) |
||||
trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") |
||||
trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .symbols, replacementString: " ") |
||||
|
||||
switch scope { |
||||
case .club: |
||||
return nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true |
||||
case .all: |
||||
return libelle?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || jugeArbitre?.nom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || |
||||
jugeArbitre?.prenom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true |
||||
case .ligue: |
||||
return nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true |
||||
} |
||||
} |
||||
} |
||||
|
||||
extension FederalTournament: FederalTournamentHolder { |
||||
var startDate: Date { dateDebut ?? .distantFuture } |
||||
var endDate: Date? { dateFin } |
||||
// var distance: Double? { distanceEnMetres } |
||||
// var localizedLabel: String? { libelle } |
||||
// var clubName: String? { nomClub } |
||||
// var importedId: Int { id } |
||||
|
||||
var holderId: String { id.string } |
||||
|
||||
func clubLabel() -> String { |
||||
nomClub ?? villeEngagement ?? installation?.nom ?? "" |
||||
} |
||||
|
||||
func subtitleLabel() -> String { |
||||
"" |
||||
} |
||||
} |
||||
|
||||
// MARK: - CategorieAge |
||||
struct CategorieAge: Codable { |
||||
var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int? |
||||
var categoriesAgeTypePratique: [CategoriesAgeTypePratique]? |
||||
var ageMax: Int? |
||||
var libelle: String? |
||||
var id: Int? |
||||
var valide, ageReel: Bool? |
||||
var ageRechercheMin: Int? |
||||
var homologable: Bool? |
||||
|
||||
var tournamentAge: FederalTournamentAge? { |
||||
if let id { |
||||
return FederalTournamentAge(rawValue: id) |
||||
} |
||||
if let libelle { |
||||
return FederalTournamentAge.allCases.first(where: { $0.localizedLabel().localizedCaseInsensitiveContains(libelle) }) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
// MARK: - CategoriesAgeTypePratique |
||||
struct CategoriesAgeTypePratique: Codable { |
||||
var id: ID? |
||||
} |
||||
|
||||
// MARK: - ID |
||||
struct ID: Codable { |
||||
var typePratique: TypePratique? |
||||
var idCategorieAge: Int? |
||||
} |
||||
|
||||
enum TypePratique: String, Codable { |
||||
case beach = "BEACH" |
||||
case padel = "PADEL" |
||||
case tennis = "TENNIS" |
||||
} |
||||
|
||||
// MARK: - CategorieTournoi |
||||
struct CategorieTournoi: Codable { |
||||
var code, codeTaxe: String? |
||||
var compteurGda: CompteurGda? |
||||
var libelle, niveauHierarchique: String? |
||||
var valide: Bool? |
||||
} |
||||
|
||||
// MARK: - CompteurGda |
||||
struct CompteurGda: Codable { |
||||
var classementMax: Classement? |
||||
var libelle: String? |
||||
var classementMin: Classement? |
||||
} |
||||
|
||||
// MARK: - Classement |
||||
struct Classement: Codable { |
||||
var nature, libelle: String? |
||||
var serie: Serie? |
||||
var sexe: String? |
||||
var id: Int? |
||||
var valide: Bool? |
||||
var poidsDouble, echelon: Int? |
||||
} |
||||
|
||||
// MARK: - Serie |
||||
struct Serie: Codable { |
||||
var code, libelle: String? |
||||
var valide: Bool? |
||||
var sexe: String? |
||||
|
||||
var tournamentCategory: TournamentCategory? { |
||||
TournamentCategory.allCases.first(where: { $0.requestLabel == code }) |
||||
} |
||||
} |
||||
|
||||
// MARK: - Epreuve |
||||
struct Epreuve: Codable { |
||||
var inscriptionEnLigneEnCours: Bool? |
||||
var categorieAge: CategorieAge? |
||||
var typeEpreuve: TypeEpreuve? |
||||
var tarifAdulte: Double? |
||||
var classementHaut: Classement? |
||||
var nombreDecoupagesPublies: Int? |
||||
var libelle: String? |
||||
var participationAgeReel, clotureInscriptionSansPreavis: Bool? |
||||
var dateCloture: Date? |
||||
var publicationResultat: Bool? |
||||
var classementBas: Classement? |
||||
var tarifsSpecifiques: [JSONAny]? |
||||
var homologuee: Bool? |
||||
var dateDebut: Date? |
||||
var tarifJeune: Double? |
||||
var serie: Serie? |
||||
var categorieAgeMax: CategorieAge? |
||||
var dateFin: Date? |
||||
var borneAnneesNaissance: BorneAnneesNaissance? |
||||
var natureEpreuve: Serie? |
||||
var epreuveOpen: Bool? |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case inscriptionEnLigneEnCours, categorieAge, tarifAdulte, classementHaut, nombreDecoupagesPublies, libelle, participationAgeReel, clotureInscriptionSansPreavis, dateCloture, publicationResultat, classementBas, tarifsSpecifiques, homologuee, dateDebut, tarifJeune, serie, categorieAgeMax, dateFin, borneAnneesNaissance, natureEpreuve, typeEpreuve |
||||
case epreuveOpen = "open" |
||||
} |
||||
|
||||
var tournamentBuild: TournamentBuild? { |
||||
if let age = categorieAge?.tournamentAge, let category = natureEpreuve?.tournamentCategory, let level = typeEpreuve?.tournamentLevel { |
||||
return TournamentBuild(category: category, level: level, age: age) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
// MARK: - TypeEpreuve |
||||
struct TypeEpreuve: Codable { |
||||
let code: String? |
||||
let delai: Int? |
||||
let libelle: String? |
||||
let coefficient, montantMax, montantMin: Int? |
||||
let valide: Bool? |
||||
let echelon: Int? |
||||
let typeHomologation: String? |
||||
|
||||
var tournamentLevel: TournamentLevel? { |
||||
if let code, let value = Int(code.removingFirstCharacter) { |
||||
return TournamentLevel(rawValue: value) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
// MARK: - BorneAnneesNaissance |
||||
struct BorneAnneesNaissance: Codable { |
||||
var min, max: Int? |
||||
} |
||||
|
||||
// MARK: - Installation |
||||
struct Installation: Codable { |
||||
var ville: String? |
||||
var lng: Double? |
||||
var surfaces: [JSONAny]? |
||||
var vestiaires: Bool? |
||||
var telephone, codePostal, nom, adresse1, adresse2: String? |
||||
var lat: Double? |
||||
var clubHouse: Bool? |
||||
|
||||
var libelle: String { |
||||
[adresse1, adresse2, ville, codePostal].compactMap({ $0 }).joined(separator: " ") |
||||
} |
||||
} |
||||
|
||||
// MARK: - JugeArbitre |
||||
struct JugeArbitre: Codable { |
||||
var idCRM, id: Int? |
||||
var nom, prenom: String? |
||||
|
||||
enum CodingKeys: String, CodingKey { |
||||
case idCRM = "idCrm" |
||||
case id, nom, prenom |
||||
} |
||||
} |
||||
|
||||
// MARK: - ModeleDeBalle |
||||
struct ModeleDeBalle: Codable { |
||||
var libelle: String? |
||||
var marqueDeBalle: MarqueDeBalle? |
||||
var id: Int? |
||||
var valide, homologue: Bool? |
||||
} |
||||
|
||||
// MARK: - MarqueDeBalle |
||||
struct MarqueDeBalle: Codable { |
||||
var id: Int? |
||||
var valide: Bool? |
||||
var marque: String? |
||||
} |
||||
|
||||
// MARK: - Encode/decode helpers |
||||
|
||||
class JSONNull: Codable, Hashable { |
||||
|
||||
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool { |
||||
return true |
||||
} |
||||
|
||||
public var hashValue: Int { |
||||
return 0 |
||||
} |
||||
|
||||
public init() {} |
||||
|
||||
public required init(from decoder: Decoder) throws { |
||||
let container = try decoder.singleValueContainer() |
||||
if !container.decodeNil() { |
||||
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) |
||||
} |
||||
} |
||||
|
||||
public func encode(to encoder: Encoder) throws { |
||||
var container = encoder.singleValueContainer() |
||||
try container.encodeNil() |
||||
} |
||||
} |
||||
|
||||
class JSONCodingKey: CodingKey { |
||||
let key: String |
||||
|
||||
required init?(intValue: Int) { |
||||
return nil |
||||
} |
||||
|
||||
required init?(stringValue: String) { |
||||
key = stringValue |
||||
} |
||||
|
||||
var intValue: Int? { |
||||
return nil |
||||
} |
||||
|
||||
var stringValue: String { |
||||
return key |
||||
} |
||||
} |
||||
|
||||
class JSONAny: Codable { |
||||
|
||||
let value: Any |
||||
|
||||
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { |
||||
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") |
||||
return DecodingError.typeMismatch(JSONAny.self, context) |
||||
} |
||||
|
||||
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { |
||||
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") |
||||
return EncodingError.invalidValue(value, context) |
||||
} |
||||
|
||||
static func decode(from container: SingleValueDecodingContainer) throws -> Any { |
||||
if let value = try? container.decode(Bool.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Int64.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Double.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(String.self) { |
||||
return value |
||||
} |
||||
if container.decodeNil() { |
||||
return JSONNull() |
||||
} |
||||
throw decodingError(forCodingPath: container.codingPath) |
||||
} |
||||
|
||||
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { |
||||
if let value = try? container.decode(Bool.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Int64.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Double.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(String.self) { |
||||
return value |
||||
} |
||||
if let value = try? container.decodeNil() { |
||||
if value { |
||||
return JSONNull() |
||||
} |
||||
} |
||||
if var container = try? container.nestedUnkeyedContainer() { |
||||
return try decodeArray(from: &container) |
||||
} |
||||
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { |
||||
return try decodeDictionary(from: &container) |
||||
} |
||||
throw decodingError(forCodingPath: container.codingPath) |
||||
} |
||||
|
||||
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any { |
||||
if let value = try? container.decode(Bool.self, forKey: key) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Int64.self, forKey: key) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(Double.self, forKey: key) { |
||||
return value |
||||
} |
||||
if let value = try? container.decode(String.self, forKey: key) { |
||||
return value |
||||
} |
||||
if let value = try? container.decodeNil(forKey: key) { |
||||
if value { |
||||
return JSONNull() |
||||
} |
||||
} |
||||
if var container = try? container.nestedUnkeyedContainer(forKey: key) { |
||||
return try decodeArray(from: &container) |
||||
} |
||||
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { |
||||
return try decodeDictionary(from: &container) |
||||
} |
||||
throw decodingError(forCodingPath: container.codingPath) |
||||
} |
||||
|
||||
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { |
||||
var arr: [Any] = [] |
||||
while !container.isAtEnd { |
||||
let value = try decode(from: &container) |
||||
arr.append(value) |
||||
} |
||||
return arr |
||||
} |
||||
|
||||
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] { |
||||
var dict = [String: Any]() |
||||
for key in container.allKeys { |
||||
let value = try decode(from: &container, forKey: key) |
||||
dict[key.stringValue] = value |
||||
} |
||||
return dict |
||||
} |
||||
|
||||
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { |
||||
for value in array { |
||||
if let value = value as? Bool { |
||||
try container.encode(value) |
||||
} else if let value = value as? Int64 { |
||||
try container.encode(value) |
||||
} else if let value = value as? Double { |
||||
try container.encode(value) |
||||
} else if let value = value as? String { |
||||
try container.encode(value) |
||||
} else if value is JSONNull { |
||||
try container.encodeNil() |
||||
} else if let value = value as? [Any] { |
||||
var container = container.nestedUnkeyedContainer() |
||||
try encode(to: &container, array: value) |
||||
} else if let value = value as? [String: Any] { |
||||
var container = container.nestedContainer(keyedBy: JSONCodingKey.self) |
||||
try encode(to: &container, dictionary: value) |
||||
} else { |
||||
throw encodingError(forValue: value, codingPath: container.codingPath) |
||||
} |
||||
} |
||||
} |
||||
|
||||
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws { |
||||
for (key, value) in dictionary { |
||||
let key = JSONCodingKey(stringValue: key)! |
||||
if let value = value as? Bool { |
||||
try container.encode(value, forKey: key) |
||||
} else if let value = value as? Int64 { |
||||
try container.encode(value, forKey: key) |
||||
} else if let value = value as? Double { |
||||
try container.encode(value, forKey: key) |
||||
} else if let value = value as? String { |
||||
try container.encode(value, forKey: key) |
||||
} else if value is JSONNull { |
||||
try container.encodeNil(forKey: key) |
||||
} else if let value = value as? [Any] { |
||||
var container = container.nestedUnkeyedContainer(forKey: key) |
||||
try encode(to: &container, array: value) |
||||
} else if let value = value as? [String: Any] { |
||||
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) |
||||
try encode(to: &container, dictionary: value) |
||||
} else { |
||||
throw encodingError(forValue: value, codingPath: container.codingPath) |
||||
} |
||||
} |
||||
} |
||||
|
||||
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { |
||||
if let value = value as? Bool { |
||||
try container.encode(value) |
||||
} else if let value = value as? Int64 { |
||||
try container.encode(value) |
||||
} else if let value = value as? Double { |
||||
try container.encode(value) |
||||
} else if let value = value as? String { |
||||
try container.encode(value) |
||||
} else if value is JSONNull { |
||||
try container.encodeNil() |
||||
} else { |
||||
throw encodingError(forValue: value, codingPath: container.codingPath) |
||||
} |
||||
} |
||||
|
||||
public required init(from decoder: Decoder) throws { |
||||
if var arrayContainer = try? decoder.unkeyedContainer() { |
||||
self.value = try JSONAny.decodeArray(from: &arrayContainer) |
||||
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { |
||||
self.value = try JSONAny.decodeDictionary(from: &container) |
||||
} else { |
||||
let container = try decoder.singleValueContainer() |
||||
self.value = try JSONAny.decode(from: container) |
||||
} |
||||
} |
||||
|
||||
public func encode(to encoder: Encoder) throws { |
||||
if let arr = self.value as? [Any] { |
||||
var container = encoder.unkeyedContainer() |
||||
try JSONAny.encode(to: &container, array: arr) |
||||
} else if let dict = self.value as? [String: Any] { |
||||
var container = encoder.container(keyedBy: JSONCodingKey.self) |
||||
try JSONAny.encode(to: &container, dictionary: dict) |
||||
} else { |
||||
var container = encoder.singleValueContainer() |
||||
try JSONAny.encode(to: &container, value: self.value) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@ |
||||
// |
||||
// FederalTournamentHolder.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 13/11/2023. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
protocol FederalTournamentHolder { |
||||
var holderId: String { get } |
||||
var startDate: Date { get } |
||||
var endDate: Date? { get } |
||||
var tournaments: [any TournamentBuildHolder] { get } |
||||
func clubLabel() -> String |
||||
func subtitleLabel() -> String |
||||
var dayDuration: Int { get } |
||||
} |
||||
|
||||
extension FederalTournamentHolder { |
||||
|
||||
func durationLabel() -> String { |
||||
switch dayDuration { |
||||
case 1: |
||||
return (startDate.timeOfDay == .evening ? "Soir" : "Journée") |
||||
default: |
||||
return dayDuration.formatted() + " jours" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,26 @@ |
||||
// |
||||
// Calendar+Extensions.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 28/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
extension Calendar { |
||||
func numberOfDaysBetween(_ from: Date?, and to: Date?) -> Int { |
||||
guard let from, let to else { return 0 } |
||||
let fromDate = startOfDay(for: from) |
||||
let toDate = startOfDay(for: to) |
||||
let numberOfDays = dateComponents([.day], from: fromDate, to: toDate) |
||||
|
||||
return numberOfDays.day! // <1> |
||||
} |
||||
|
||||
func isSameDay(date1: Date?, date2: Date?) -> Bool { |
||||
guard let date1, let date2 else { return false } |
||||
|
||||
return numberOfDaysBetween(date1, and: date2) == 0 |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
// |
||||
// FederalTournamentSearchScope.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 28/03/2024. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
enum FederalTournamentSearchScope: String, CaseIterable { |
||||
case all = "Tout" |
||||
case club = "Club" |
||||
case ligue = "Ligue" |
||||
} |
||||
@ -0,0 +1,120 @@ |
||||
// |
||||
// Created for Custom Calendar |
||||
// by Stewart Lynch on 2024-01-22 |
||||
// |
||||
// Follow me on Mastodon: @StewartLynch@iosdev.space |
||||
// Follow me on Threads: @StewartLynch (https://www.threads.net) |
||||
// Follow me on X: https://x.com/StewartLynch |
||||
// Follow me on LinkedIn: https://linkedin.com/in/StewartLynch |
||||
// Subscribe on YouTube: https://youTube.com/@StewartLynch |
||||
// Buy me a ko-fi: https://ko-fi.com/StewartLynch |
||||
|
||||
|
||||
import SwiftUI |
||||
|
||||
struct CalendarView: View { |
||||
let date: Date |
||||
let tournaments: [FederalTournamentHolder] |
||||
let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays |
||||
let columns = Array(repeating: GridItem(.flexible()), count: 7) |
||||
@State private var days: [Date] = [] |
||||
@State private var counts = [Int : Int]() |
||||
|
||||
// Extension properties to display selected day's workouts |
||||
@State private var selectedDay: Date? |
||||
@State private var tournamentsByDay: [FederalTournamentHolder] = [] |
||||
|
||||
@ViewBuilder |
||||
var body: some View { |
||||
let color = Color.blue |
||||
VStack { |
||||
HStack { |
||||
ForEach(daysOfWeek.indices, id: \.self) { index in |
||||
Text(daysOfWeek[index]) |
||||
.fontWeight(.black) |
||||
.foregroundStyle(color) |
||||
.frame(maxWidth: .infinity) |
||||
} |
||||
} |
||||
LazyVGrid(columns: columns) { |
||||
ForEach(days, id: \.self) { day in |
||||
if day.monthInt != date.monthInt { |
||||
Text("") |
||||
} else { |
||||
Text(day.formatted(.dateTime.day())) |
||||
.fontWeight(.bold) |
||||
.foregroundStyle(.secondary) |
||||
.frame(maxWidth: .infinity, minHeight: 40) |
||||
.background( |
||||
Circle() |
||||
.foregroundStyle( |
||||
Date.now.startOfDay == day.startOfDay |
||||
? .red.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) |
||||
: color.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) |
||||
) |
||||
) |
||||
.overlay(alignment: .bottomTrailing) { |
||||
if let count = counts[day.dayInt] { |
||||
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") |
||||
.foregroundColor(.secondary) |
||||
.imageScale(.medium) |
||||
.background ( |
||||
Color(.systemBackground) |
||||
.clipShape(.circle) |
||||
) |
||||
.offset(x: 5, y: 5) |
||||
} |
||||
} |
||||
.onTapGesture { |
||||
// Used in the ExtendedProject branch |
||||
if let count = counts[day.dayInt], count > 0 { |
||||
selectedDay = day |
||||
} else { |
||||
selectedDay = nil |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.onAppear { |
||||
days = date.calendarDisplayDays |
||||
setupCounts() |
||||
selectedDay = nil // Used to present list of workouts when tapped |
||||
} |
||||
.onChange(of: date) { |
||||
days = date.calendarDisplayDays |
||||
setupCounts() |
||||
selectedDay = nil // Used to present list of workouts when tapped |
||||
} |
||||
.onChange(of: selectedDay) { |
||||
// Will filter the workouts for the specific day and activity is selected |
||||
if let selectedDay { |
||||
tournamentsByDay = tournaments.filter {$0.startDate.dayInt == selectedDay.dayInt} |
||||
} |
||||
} |
||||
// |
||||
// if let selectedDay { |
||||
// // Presents the list of workouts for the selected day and activity |
||||
// ForEach(tournamentsByDay) { tournament in |
||||
// NavigationLink(value: tournament) { |
||||
// HStack { |
||||
// Text(tournament.title()) |
||||
// Spacer() |
||||
// Text(tournament.sortedTeams().count.formatted()) |
||||
// } |
||||
// } |
||||
// } |
||||
// } |
||||
} |
||||
|
||||
func setupCounts() { |
||||
let filteredTournaments = tournaments |
||||
let mappedItems = filteredTournaments.map{($0.startDate.dayInt, $0.tournaments.count)} |
||||
counts = Dictionary(mappedItems, uniquingKeysWith: +) |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
CalendarView(date: .now, tournaments: []) |
||||
} |
||||
@ -0,0 +1,59 @@ |
||||
// |
||||
// TenupEventListView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 28/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct TenupEventListView: View { |
||||
@EnvironmentObject var dataStore: DataStore |
||||
|
||||
let tournaments: [FederalTournament] |
||||
let viewStyle: AgendaDestination.ViewStyle |
||||
|
||||
var body: some View { |
||||
let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.startOfMonth } |
||||
|
||||
ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in |
||||
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) { |
||||
Section { |
||||
switch viewStyle { |
||||
case .list: |
||||
_listView(_tournaments) |
||||
case .calendar: |
||||
CalendarView(date: section, tournaments: _tournaments) |
||||
} |
||||
} header: { |
||||
HStack { |
||||
Text(section.monthYearFormatted) |
||||
Spacer() |
||||
Text(_tournaments.map { $0.tournaments.count }.reduce(0,+).formatted()) |
||||
} |
||||
} |
||||
.headerProminence(.increased) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private func _listView(_ tournaments: [FederalTournament]) -> some View { |
||||
ForEach(tournaments, id: \.holderId) { tournament in |
||||
TournamentCellView(tournament: tournament) |
||||
} |
||||
} |
||||
|
||||
/* |
||||
Button("importer") { |
||||
let event = dataStore.events.first(where: { $0.tenupId == tournament.id.string }) ?? Event(tenupId: tournament.id.string) |
||||
let tournament = Tournament(event: event.id, ) |
||||
} |
||||
.buttonStyle(.bordered) |
||||
.buttonBorderShape(.capsule) |
||||
|
||||
*/ |
||||
} |
||||
|
||||
#Preview { |
||||
TenupEventListView(tournaments: [], viewStyle: .calendar) |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
// |
||||
// PadelClubButtonView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 29/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PadelClubButtonView: View { |
||||
let isImported: Bool |
||||
|
||||
var body: some View { |
||||
Image(.logo) |
||||
.resizable() |
||||
.scaledToFit() |
||||
.frame(width: 44) |
||||
.overlay(alignment: .bottomTrailing) { |
||||
if isImported { |
||||
Image(systemName: "checkmark.circle.fill") |
||||
.foregroundColor(.green) |
||||
.imageScale(.medium) |
||||
.background ( |
||||
Color(.systemBackground) |
||||
.clipShape(.circle) |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
#Preview { |
||||
PadelClubButtonView(isImported: true) |
||||
} |
||||
Loading…
Reference in new issue