Showing
6 changed files
with
332 additions
and
9 deletions
This diff is collapsed. Click to expand it.
| ... | @@ -2194,8 +2194,88 @@ public final class WarplySDK { | ... | @@ -2194,8 +2194,88 @@ public final class WarplySDK { |
| 2194 | } | 2194 | } |
| 2195 | } | 2195 | } |
| 2196 | 2196 | ||
| 2197 | - // MARK: - Profile | 2197 | + // MARK: - Merchant Categories |
| 2198 | + | ||
| 2199 | + /// Get merchant categories | ||
| 2200 | + /// - Parameters: | ||
| 2201 | + /// - language: Language for the categories (optional, defaults to applicationLocale) | ||
| 2202 | + /// - completion: Completion handler with merchant categories array | ||
| 2203 | + /// - failureCallback: Failure callback with error code | ||
| 2204 | + public func getMerchantCategories( | ||
| 2205 | + language: String? = nil, | ||
| 2206 | + completion: @escaping ([MerchantCategoryModel]?) -> Void, | ||
| 2207 | + failureCallback: @escaping (Int) -> Void | ||
| 2208 | + ) { | ||
| 2209 | + let finalLanguage = language ?? self.applicationLocale | ||
| 2210 | + | ||
| 2211 | + Task { | ||
| 2212 | + do { | ||
| 2213 | + let endpoint = Endpoint.getMerchantCategories(language: finalLanguage) | ||
| 2214 | + let response = try await networkService.requestRaw(endpoint) | ||
| 2215 | + | ||
| 2216 | + await MainActor.run { | ||
| 2217 | + if response["status"] as? Int == 1 { | ||
| 2218 | + // Success analytics | ||
| 2219 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2220 | + dynatraceEvent._eventName = "custom_success_get_merchant_categories_loyalty" | ||
| 2221 | + dynatraceEvent._parameters = nil | ||
| 2222 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2223 | + | ||
| 2224 | + var categories: [MerchantCategoryModel] = [] | ||
| 2225 | + | ||
| 2226 | + // Parse from context.MAPP_SHOPS.result structure | ||
| 2227 | + if let mappShops = response["MAPP_SHOPS"] as? [String: Any], | ||
| 2228 | + let result = mappShops["result"] as? [[String: Any]] { | ||
| 2229 | + | ||
| 2230 | + for categoryDict in result { | ||
| 2231 | + let category = MerchantCategoryModel(dictionary: categoryDict) | ||
| 2232 | + categories.append(category) | ||
| 2233 | + } | ||
| 2234 | + | ||
| 2235 | + print("✅ [WarplySDK] Retrieved \(categories.count) merchant categories") | ||
| 2236 | + completion(categories) | ||
| 2237 | + } else { | ||
| 2238 | + print("⚠️ [WarplySDK] No merchant categories found in response") | ||
| 2239 | + completion([]) | ||
| 2240 | + } | ||
| 2241 | + } else { | ||
| 2242 | + // Error analytics | ||
| 2243 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2244 | + dynatraceEvent._eventName = "custom_error_get_merchant_categories_loyalty" | ||
| 2245 | + dynatraceEvent._parameters = nil | ||
| 2246 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2247 | + | ||
| 2248 | + failureCallback(-1) | ||
| 2249 | + } | ||
| 2250 | + } | ||
| 2251 | + } catch { | ||
| 2252 | + await MainActor.run { | ||
| 2253 | + self.handleError(error, context: "getMerchantCategories", endpoint: "getMerchantCategories", failureCallback: failureCallback) | ||
| 2254 | + } | ||
| 2255 | + } | ||
| 2256 | + } | ||
| 2257 | + } | ||
| 2198 | 2258 | ||
| 2259 | + /// Get merchant categories (async/await variant) | ||
| 2260 | + /// - Parameter language: Language for the categories (optional, defaults to applicationLocale) | ||
| 2261 | + /// - Returns: Array of merchant categories | ||
| 2262 | + /// - Throws: WarplyError if the request fails | ||
| 2263 | + public func getMerchantCategories(language: String? = nil) async throws -> [MerchantCategoryModel] { | ||
| 2264 | + return try await withCheckedThrowingContinuation { continuation in | ||
| 2265 | + getMerchantCategories(language: language, completion: { categories in | ||
| 2266 | + if let categories = categories { | ||
| 2267 | + continuation.resume(returning: categories) | ||
| 2268 | + } else { | ||
| 2269 | + continuation.resume(throwing: WarplyError.networkError) | ||
| 2270 | + } | ||
| 2271 | + }, failureCallback: { errorCode in | ||
| 2272 | + continuation.resume(throwing: WarplyError.unknownError(errorCode)) | ||
| 2273 | + }) | ||
| 2274 | + } | ||
| 2275 | + } | ||
| 2276 | + | ||
| 2277 | + // MARK: - Profile | ||
| 2278 | + | ||
| 2199 | /// Get user profile details | 2279 | /// Get user profile details |
| 2200 | /// - Parameters: | 2280 | /// - Parameters: |
| 2201 | /// - completion: Completion handler with profile model | 2281 | /// - completion: Completion handler with profile model | ... | ... |
| ... | @@ -71,6 +71,7 @@ public enum Endpoint { | ... | @@ -71,6 +71,7 @@ public enum Endpoint { |
| 71 | // Market & Merchants | 71 | // Market & Merchants |
| 72 | case getMarketPassDetails | 72 | case getMarketPassDetails |
| 73 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) | 73 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) |
| 74 | + case getMerchantCategories(language: String) | ||
| 74 | 75 | ||
| 75 | // Card Management | 76 | // Card Management |
| 76 | case addCard(cardNumber: String, cardIssuer: String, cardHolder: String, expirationMonth: String, expirationYear: String) | 77 | case addCard(cardNumber: String, cardIssuer: String, cardHolder: String, expirationMonth: String, expirationYear: String) |
| ... | @@ -126,7 +127,7 @@ public enum Endpoint { | ... | @@ -126,7 +127,7 @@ public enum Endpoint { |
| 126 | return "/user/v5/{appUUID}/logout" | 127 | return "/user/v5/{appUUID}/logout" |
| 127 | 128 | ||
| 128 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ | 129 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ |
| 129 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets: | 130 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories: |
| 130 | return "/api/mobile/v2/{appUUID}/context/" | 131 | return "/api/mobile/v2/{appUUID}/context/" |
| 131 | 132 | ||
| 132 | // Authenticated Context endpoints - /oauth/{appUUID}/context | 133 | // Authenticated Context endpoints - /oauth/{appUUID}/context |
| ... | @@ -159,7 +160,7 @@ public enum Endpoint { | ... | @@ -159,7 +160,7 @@ public enum Endpoint { |
| 159 | switch self { | 160 | switch self { |
| 160 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, | 161 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, |
| 161 | .getCoupons, .getCouponSets, .getAvailableCoupons, | 162 | .getCoupons, .getCouponSets, .getAvailableCoupons, |
| 162 | - .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .sendEvent, .sendDeviceInfo, .getCosmoteUser: | 163 | + .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .sendEvent, .sendDeviceInfo, .getCosmoteUser: |
| 163 | return .POST | 164 | return .POST |
| 164 | case .getSingleCampaign, .getNetworkStatus: | 165 | case .getSingleCampaign, .getNetworkStatus: |
| 165 | return .GET | 166 | return .GET |
| ... | @@ -379,6 +380,15 @@ public enum Endpoint { | ... | @@ -379,6 +380,15 @@ public enum Endpoint { |
| 379 | ] | 380 | ] |
| 380 | ] | 381 | ] |
| 381 | 382 | ||
| 383 | + // Merchant Categories - using shops structure for DEI API | ||
| 384 | + case .getMerchantCategories(let language): | ||
| 385 | + return [ | ||
| 386 | + "shops": [ | ||
| 387 | + "language": language, | ||
| 388 | + "action": "retrieve_categories" | ||
| 389 | + ] | ||
| 390 | + ] | ||
| 391 | + | ||
| 382 | // Analytics endpoints - events structure | 392 | // Analytics endpoints - events structure |
| 383 | case .sendEvent(let eventName, let priority): | 393 | case .sendEvent(let eventName, let priority): |
| 384 | return [ | 394 | return [ |
| ... | @@ -434,7 +444,7 @@ public enum Endpoint { | ... | @@ -434,7 +444,7 @@ public enum Endpoint { |
| 434 | return .userManagement | 444 | return .userManagement |
| 435 | 445 | ||
| 436 | // Standard Context - /api/mobile/v2/{appUUID}/context/ | 446 | // Standard Context - /api/mobile/v2/{appUUID}/context/ |
| 437 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets: | 447 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories: |
| 438 | return .standardContext | 448 | return .standardContext |
| 439 | 449 | ||
| 440 | // Authenticated Context - /oauth/{appUUID}/context | 450 | // Authenticated Context - /oauth/{appUUID}/context |
| ... | @@ -476,7 +486,7 @@ public enum Endpoint { | ... | @@ -476,7 +486,7 @@ public enum Endpoint { |
| 476 | // Standard Authentication (loyalty headers only) | 486 | // Standard Authentication (loyalty headers only) |
| 477 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, | 487 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, |
| 478 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, | 488 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, |
| 479 | - .getMerchants, .getNetworkStatus: | 489 | + .getMerchants, .getMerchantCategories, .getNetworkStatus: |
| 480 | return .standard | 490 | return .standard |
| 481 | 491 | ||
| 482 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | 492 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | ... | ... |
| ... | @@ -962,6 +962,22 @@ extension NetworkService { | ... | @@ -962,6 +962,22 @@ extension NetworkService { |
| 962 | return response | 962 | return response |
| 963 | } | 963 | } |
| 964 | 964 | ||
| 965 | + // MARK: - Merchant Categories Methods | ||
| 966 | + | ||
| 967 | + /// Get merchant categories | ||
| 968 | + /// - Parameter language: Language for the categories | ||
| 969 | + /// - Returns: Response dictionary containing merchant categories | ||
| 970 | + /// - Throws: NetworkError if request fails | ||
| 971 | + public func getMerchantCategories(language: String) async throws -> [String: Any] { | ||
| 972 | + print("🔄 [NetworkService] Getting merchant categories for language: \(language)") | ||
| 973 | + let endpoint = Endpoint.getMerchantCategories(language: language) | ||
| 974 | + let response = try await requestRaw(endpoint) | ||
| 975 | + | ||
| 976 | + print("✅ [NetworkService] Get merchant categories request completed") | ||
| 977 | + | ||
| 978 | + return response | ||
| 979 | + } | ||
| 980 | + | ||
| 965 | // MARK: - Coupon Operations Methods | 981 | // MARK: - Coupon Operations Methods |
| 966 | 982 | ||
| 967 | /// Validate a coupon for the user | 983 | /// Validate a coupon for the user | ... | ... |
| 1 | +// | ||
| 2 | +// MerchantCategoryModel.swift | ||
| 3 | +// SwiftWarplyFramework | ||
| 4 | +// | ||
| 5 | +// Created by Warply on 28/07/2025. | ||
| 6 | +// Copyright © 2025 Warply. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +import Foundation | ||
| 10 | + | ||
| 11 | +// MARK: - Merchant Category Model | ||
| 12 | + | ||
| 13 | +public class MerchantCategoryModel: NSObject { | ||
| 14 | + private var uuid: String? | ||
| 15 | + private var admin_name: String? | ||
| 16 | + private var image: String? | ||
| 17 | + private var parent: String? | ||
| 18 | + private var fields: String? | ||
| 19 | + private var children: [Any]? | ||
| 20 | + private var count: Int? | ||
| 21 | + private var name: String? | ||
| 22 | + | ||
| 23 | + public init() { | ||
| 24 | + self.uuid = "" | ||
| 25 | + self.admin_name = "" | ||
| 26 | + self.image = "" | ||
| 27 | + self.parent = "" | ||
| 28 | + self.fields = "" | ||
| 29 | + self.children = [] | ||
| 30 | + self.count = 0 | ||
| 31 | + self.name = "" | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + public init(dictionary: [String: Any]) { | ||
| 35 | + self.uuid = dictionary["uuid"] as? String ?? "" | ||
| 36 | + self.admin_name = dictionary["admin_name"] as? String ?? "" | ||
| 37 | + self.image = dictionary["image"] as? String ?? "" | ||
| 38 | + self.parent = dictionary["parent"] as? String | ||
| 39 | + self.fields = dictionary["fields"] as? String ?? "" | ||
| 40 | + self.children = dictionary["children"] as? [Any] ?? [] | ||
| 41 | + self.count = dictionary["count"] as? Int ?? 0 | ||
| 42 | + self.name = dictionary["name"] as? String | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // MARK: - Public Accessors | ||
| 46 | + | ||
| 47 | + public var _uuid: String { | ||
| 48 | + get { return self.uuid ?? "" } | ||
| 49 | + set(newValue) { self.uuid = newValue } | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public var _admin_name: String { | ||
| 53 | + get { return self.admin_name ?? "" } | ||
| 54 | + set(newValue) { self.admin_name = newValue } | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public var _image: String { | ||
| 58 | + get { return self.image ?? "" } | ||
| 59 | + set(newValue) { self.image = newValue } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + public var _parent: String? { | ||
| 63 | + get { return self.parent } | ||
| 64 | + set(newValue) { self.parent = newValue } | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public var _fields: String { | ||
| 68 | + get { return self.fields ?? "" } | ||
| 69 | + set(newValue) { self.fields = newValue } | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + public var _children: [Any] { | ||
| 73 | + get { return self.children ?? [] } | ||
| 74 | + set(newValue) { self.children = newValue } | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + public var _count: Int { | ||
| 78 | + get { return self.count ?? 0 } | ||
| 79 | + set(newValue) { self.count = newValue } | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + public var _name: String? { | ||
| 83 | + get { return self.name } | ||
| 84 | + set(newValue) { self.name = newValue } | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + // MARK: - Computed Properties | ||
| 88 | + | ||
| 89 | + /// Display name for the category - uses name if available, otherwise falls back to admin_name | ||
| 90 | + public var displayName: String { | ||
| 91 | + return self.name ?? self.admin_name ?? "" | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /// Clean image URL with whitespace trimmed | ||
| 95 | + public var cleanImageUrl: String { | ||
| 96 | + return self.image?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /// Check if this category has a parent category | ||
| 100 | + public var hasParent: Bool { | ||
| 101 | + return self.parent != nil && !(self.parent?.isEmpty ?? true) | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /// Check if this category has child categories | ||
| 105 | + public var hasChildren: Bool { | ||
| 106 | + return !self.children.isEmpty | ||
| 107 | + } | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +// MARK: - Codable Support | ||
| 111 | + | ||
| 112 | +extension MerchantCategoryModel: Codable { | ||
| 113 | + private enum CodingKeys: String, CodingKey { | ||
| 114 | + case uuid | ||
| 115 | + case admin_name | ||
| 116 | + case image | ||
| 117 | + case parent | ||
| 118 | + case fields | ||
| 119 | + case children | ||
| 120 | + case count | ||
| 121 | + case name | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + public func encode(to encoder: Encoder) throws { | ||
| 125 | + var container = encoder.container(keyedBy: CodingKeys.self) | ||
| 126 | + try container.encode(uuid, forKey: .uuid) | ||
| 127 | + try container.encode(admin_name, forKey: .admin_name) | ||
| 128 | + try container.encode(image, forKey: .image) | ||
| 129 | + try container.encodeIfPresent(parent, forKey: .parent) | ||
| 130 | + try container.encode(fields, forKey: .fields) | ||
| 131 | + try container.encode(count, forKey: .count) | ||
| 132 | + try container.encodeIfPresent(name, forKey: .name) | ||
| 133 | + // Note: children is [Any] so we skip encoding it for now | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + public required init(from decoder: Decoder) throws { | ||
| 137 | + let container = try decoder.container(keyedBy: CodingKeys.self) | ||
| 138 | + self.uuid = try container.decodeIfPresent(String.self, forKey: .uuid) | ||
| 139 | + self.admin_name = try container.decodeIfPresent(String.self, forKey: .admin_name) | ||
| 140 | + self.image = try container.decodeIfPresent(String.self, forKey: .image) | ||
| 141 | + self.parent = try container.decodeIfPresent(String.self, forKey: .parent) | ||
| 142 | + self.fields = try container.decodeIfPresent(String.self, forKey: .fields) | ||
| 143 | + self.count = try container.decodeIfPresent(Int.self, forKey: .count) | ||
| 144 | + self.name = try container.decodeIfPresent(String.self, forKey: .name) | ||
| 145 | + self.children = [] // Default empty array for children | ||
| 146 | + } | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +// MARK: - Debug Description | ||
| 150 | + | ||
| 151 | +extension MerchantCategoryModel { | ||
| 152 | + public override var description: String { | ||
| 153 | + return """ | ||
| 154 | + MerchantCategoryModel { | ||
| 155 | + uuid: \(_uuid) | ||
| 156 | + displayName: \(displayName) | ||
| 157 | + admin_name: \(_admin_name) | ||
| 158 | + image: \(cleanImageUrl) | ||
| 159 | + parent: \(_parent ?? "nil") | ||
| 160 | + count: \(_count) | ||
| 161 | + hasChildren: \(hasChildren) | ||
| 162 | + } | ||
| 163 | + """ | ||
| 164 | + } | ||
| 165 | +} |
| ... | @@ -46,6 +46,9 @@ import UIKit | ... | @@ -46,6 +46,9 @@ import UIKit |
| 46 | // Merchants data | 46 | // Merchants data |
| 47 | var merchants: [MerchantModel] = [] | 47 | var merchants: [MerchantModel] = [] |
| 48 | 48 | ||
| 49 | + // Merchant categories data | ||
| 50 | + var merchantCategories: [MerchantCategoryModel] = [] | ||
| 51 | + | ||
| 49 | // Profile data | 52 | // Profile data |
| 50 | var profileModel: ProfileModel? | 53 | var profileModel: ProfileModel? |
| 51 | var profileSection: SectionModel? | 54 | var profileSection: SectionModel? |
| ... | @@ -176,9 +179,8 @@ import UIKit | ... | @@ -176,9 +179,8 @@ import UIKit |
| 176 | self.merchants = merchants | 179 | self.merchants = merchants |
| 177 | print("✅ [MyRewardsViewController] Loaded \(merchants.count) merchants") | 180 | print("✅ [MyRewardsViewController] Loaded \(merchants.count) merchants") |
| 178 | 181 | ||
| 179 | - // For now, create the coupon sets section without filtering | 182 | + // Load merchant categories after merchants success |
| 180 | - // Later this will be enhanced to filter by merchant categories | 183 | + self.loadMerchantCategories() |
| 181 | - self.createCouponSetsSection() | ||
| 182 | 184 | ||
| 183 | } failureCallback: { [weak self] errorCode in | 185 | } failureCallback: { [weak self] errorCode in |
| 184 | print("Failed to load merchants: \(errorCode)") | 186 | print("Failed to load merchants: \(errorCode)") |
| ... | @@ -187,8 +189,58 @@ import UIKit | ... | @@ -187,8 +189,58 @@ import UIKit |
| 187 | } | 189 | } |
| 188 | } | 190 | } |
| 189 | 191 | ||
| 192 | + // MARK: - Merchant Categories Loading | ||
| 193 | + private func loadMerchantCategories() { | ||
| 194 | + // Load merchant categories from WarplySDK | ||
| 195 | + WarplySDK.shared.getMerchantCategories { [weak self] categories in | ||
| 196 | + guard let self = self, let categories = categories else { | ||
| 197 | + // If categories fail to load, still create coupon sets section without filtering | ||
| 198 | + self?.createCouponSetsSection() | ||
| 199 | + return | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + self.merchantCategories = categories | ||
| 203 | + print("✅ [MyRewardsViewController] Loaded \(categories.count) merchant categories") | ||
| 204 | + | ||
| 205 | + // TODO: Implement category-based filtering for coupon sets sections | ||
| 206 | + // For now, create the standard coupon sets section | ||
| 207 | + self.createCouponSetsSection() | ||
| 208 | + | ||
| 209 | + } failureCallback: { [weak self] errorCode in | ||
| 210 | + print("Failed to load merchant categories: \(errorCode)") | ||
| 211 | + // If categories fail, still show coupon sets without filtering | ||
| 212 | + self?.createCouponSetsSection() | ||
| 213 | + } | ||
| 214 | + } | ||
| 215 | + | ||
| 190 | private func createCouponSetsSection() { | 216 | private func createCouponSetsSection() { |
| 191 | - // Create coupon sets section with real data | 217 | + // TODO: IMPLEMENT CATEGORY-BASED FILTERING |
| 218 | + // | ||
| 219 | + // Current logic: Creates one section with all coupon sets | ||
| 220 | + // | ||
| 221 | + // Future enhancement: Filter coupon sets into different sections based on categories | ||
| 222 | + // Logic: | ||
| 223 | + // 1. For each couponset, get its merchant_uuid | ||
| 224 | + // 2. Find the merchant with that merchant_uuid in self.merchants | ||
| 225 | + // 3. Get the merchant's category_uuid | ||
| 226 | + // 4. Find the category with that category_uuid in self.merchantCategories | ||
| 227 | + // 5. Group coupon sets by category | ||
| 228 | + // 6. Create separate sections for each category | ||
| 229 | + // | ||
| 230 | + // Example structure after filtering: | ||
| 231 | + // - Section: "Εκπαίδευση" (Education) - coupon sets from education merchants | ||
| 232 | + // - Section: "Ψυχαγωγία" (Entertainment) - coupon sets from entertainment merchants | ||
| 233 | + // - etc. | ||
| 234 | + // | ||
| 235 | + // Implementation steps: | ||
| 236 | + // 1. Create a dictionary to group coupon sets by category: [String: [CouponSetItemModel]] | ||
| 237 | + // 2. Iterate through self.couponSets | ||
| 238 | + // 3. For each coupon set, find its merchant and category | ||
| 239 | + // 4. Add coupon set to the appropriate category group | ||
| 240 | + // 5. Create a SectionModel for each category group | ||
| 241 | + // 6. Sort sections by category name or priority | ||
| 242 | + | ||
| 243 | + // Current implementation (temporary): | ||
| 192 | if !self.couponSets.isEmpty { | 244 | if !self.couponSets.isEmpty { |
| 193 | let couponSetsSection = SectionModel( | 245 | let couponSetsSection = SectionModel( |
| 194 | sectionType: .myRewardsHorizontalCouponsets, | 246 | sectionType: .myRewardsHorizontalCouponsets, | ... | ... |
-
Please register or login to post a comment