new getCouponFilters request and MyRewardsViewController sections handling
Showing
4 changed files
with
284 additions
and
179 deletions
| ... | @@ -438,6 +438,7 @@ private final class SDKState { | ... | @@ -438,6 +438,7 @@ private final class SDKState { |
| 438 | var carouselList: [CampaignItemModel] = [] | 438 | var carouselList: [CampaignItemModel] = [] |
| 439 | var marketPassDetails: MarketPassDetailsModel? | 439 | var marketPassDetails: MarketPassDetailsModel? |
| 440 | var supermarketCampaign: CampaignItemModel? | 440 | var supermarketCampaign: CampaignItemModel? |
| 441 | + var couponFilters: CouponFiltersDataModel? | ||
| 441 | 442 | ||
| 442 | private init() {} | 443 | private init() {} |
| 443 | } | 444 | } |
| ... | @@ -2545,6 +2546,77 @@ public final class WarplySDK { | ... | @@ -2545,6 +2546,77 @@ public final class WarplySDK { |
| 2545 | } | 2546 | } |
| 2546 | } | 2547 | } |
| 2547 | 2548 | ||
| 2549 | + /// Get coupon filters via new endpoint | ||
| 2550 | + /// - Parameters: | ||
| 2551 | + /// - language: Optional language code (defaults to applicationLocale) | ||
| 2552 | + /// - completion: Returns the raw response dictionary on success | ||
| 2553 | + /// - failureCallback: Returns error code on failure | ||
| 2554 | + public func getCouponFilters( | ||
| 2555 | + language: String? = nil, | ||
| 2556 | + completion: @escaping (CouponFiltersDataModel?) -> Void, | ||
| 2557 | + failureCallback: @escaping (Int) -> Void | ||
| 2558 | + ) { | ||
| 2559 | + let finalLanguage = language ?? self.applicationLocale | ||
| 2560 | + | ||
| 2561 | + Task { | ||
| 2562 | + do { | ||
| 2563 | + let endpoint = Endpoint.getCouponFilters(language: finalLanguage) | ||
| 2564 | + let response = try await networkService.requestRaw(endpoint) | ||
| 2565 | + | ||
| 2566 | + await MainActor.run { | ||
| 2567 | + print("📥 getCouponFilters response: \(response)") | ||
| 2568 | + | ||
| 2569 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2570 | + dynatraceEvent._eventName = "custom_success_couponfilters_loyalty" | ||
| 2571 | + dynatraceEvent._parameters = nil | ||
| 2572 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2573 | + | ||
| 2574 | + if let resultData = response["result"] as? [String: Any] { | ||
| 2575 | + let filterModel = CouponFiltersDataModel(dictionary: resultData) | ||
| 2576 | + self.setCouponFilters(filterModel) | ||
| 2577 | + completion(filterModel) | ||
| 2578 | + } else { | ||
| 2579 | + completion(nil) | ||
| 2580 | + } | ||
| 2581 | + } | ||
| 2582 | + } catch { | ||
| 2583 | + await MainActor.run { | ||
| 2584 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2585 | + dynatraceEvent._eventName = "custom_error_couponfilters_loyalty" | ||
| 2586 | + dynatraceEvent._parameters = nil | ||
| 2587 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2588 | + | ||
| 2589 | + if let networkError = error as? NetworkError { | ||
| 2590 | + failureCallback(networkError.code) | ||
| 2591 | + } else { | ||
| 2592 | + failureCallback(-1) | ||
| 2593 | + } | ||
| 2594 | + } | ||
| 2595 | + } | ||
| 2596 | + } | ||
| 2597 | + } | ||
| 2598 | + | ||
| 2599 | + /// Get coupon filters (async/await variant) | ||
| 2600 | + /// - Parameters: | ||
| 2601 | + /// - language: Language code for localized content (optional, defaults to applicationLocale) | ||
| 2602 | + /// - Returns: Parsed filters model | ||
| 2603 | + /// - Throws: WarplyError if the request fails | ||
| 2604 | + public func getCouponFilters( | ||
| 2605 | + language: String? = nil | ||
| 2606 | + ) async throws -> CouponFiltersDataModel { | ||
| 2607 | + return try await withCheckedThrowingContinuation { continuation in | ||
| 2608 | + getCouponFilters(language: language, completion: { response in | ||
| 2609 | + if let response = response { | ||
| 2610 | + continuation.resume(returning: response) | ||
| 2611 | + } else { | ||
| 2612 | + continuation.resume(throwing: WarplyError.networkError) | ||
| 2613 | + } | ||
| 2614 | + }, failureCallback: { errorCode in | ||
| 2615 | + continuation.resume(throwing: WarplyError.unknownError(errorCode)) | ||
| 2616 | + }) | ||
| 2617 | + } | ||
| 2618 | + } | ||
| 2619 | + | ||
| 2548 | /// Get available coupons (async/await variant) | 2620 | /// Get available coupons (async/await variant) |
| 2549 | /// - Returns: Dictionary of coupon availability data | 2621 | /// - Returns: Dictionary of coupon availability data |
| 2550 | /// - Throws: WarplyError if the request fails | 2622 | /// - Throws: WarplyError if the request fails |
| ... | @@ -4344,6 +4416,16 @@ public final class WarplySDK { | ... | @@ -4344,6 +4416,16 @@ public final class WarplySDK { |
| 4344 | return state.couponSets | 4416 | return state.couponSets |
| 4345 | } | 4417 | } |
| 4346 | 4418 | ||
| 4419 | + /// Set coupon filters data | ||
| 4420 | + public func setCouponFilters(_ filters: CouponFiltersDataModel) { | ||
| 4421 | + state.couponFilters = filters | ||
| 4422 | + } | ||
| 4423 | + | ||
| 4424 | + /// Get coupon filters data | ||
| 4425 | + public func getCouponFilters() -> CouponFiltersDataModel? { | ||
| 4426 | + return state.couponFilters | ||
| 4427 | + } | ||
| 4428 | + | ||
| 4347 | /// Set seasonal list | 4429 | /// Set seasonal list |
| 4348 | public func setSeasonalList(_ seasonalCoupons: [LoyaltyGiftsForYouPackage]) { | 4430 | public func setSeasonalList(_ seasonalCoupons: [LoyaltyGiftsForYouPackage]) { |
| 4349 | state.seasonalList = seasonalCoupons | 4431 | state.seasonalList = seasonalCoupons | ... | ... |
| ... | @@ -68,6 +68,7 @@ public enum Endpoint { | ... | @@ -68,6 +68,7 @@ public enum Endpoint { |
| 68 | case getCoupons(language: String, couponsetType: String) | 68 | case getCoupons(language: String, couponsetType: String) |
| 69 | case getCouponSets(language: String, active: Bool, visible: Bool, uuids: [String]?) | 69 | case getCouponSets(language: String, active: Bool, visible: Bool, uuids: [String]?) |
| 70 | case getCouponSetsNew(language: String, active: Bool, visible: Bool, region: String?, offerCategory: String?) | 70 | case getCouponSetsNew(language: String, active: Bool, visible: Bool, region: String?, offerCategory: String?) |
| 71 | + case getCouponFilters(language: String) | ||
| 71 | case getAvailableCoupons | 72 | case getAvailableCoupons |
| 72 | 73 | ||
| 73 | // Market & Merchants | 74 | // Market & Merchants |
| ... | @@ -143,7 +144,7 @@ public enum Endpoint { | ... | @@ -143,7 +144,7 @@ public enum Endpoint { |
| 143 | return "/api/mobile/v2/{appUUID}/context/" | 144 | return "/api/mobile/v2/{appUUID}/context/" |
| 144 | 145 | ||
| 145 | // Authenticated Context endpoints - /oauth/{appUUID}/context | 146 | // Authenticated Context endpoints - /oauth/{appUUID}/context |
| 146 | - case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew: | 147 | + case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew, .getCouponFilters: |
| 147 | return "/oauth/{appUUID}/context" | 148 | return "/oauth/{appUUID}/context" |
| 148 | 149 | ||
| 149 | // Session endpoints - /api/session/{sessionUuid} | 150 | // Session endpoints - /api/session/{sessionUuid} |
| ... | @@ -171,7 +172,7 @@ public enum Endpoint { | ... | @@ -171,7 +172,7 @@ public enum Endpoint { |
| 171 | public var method: HTTPMethod { | 172 | public var method: HTTPMethod { |
| 172 | switch self { | 173 | switch self { |
| 173 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, | 174 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, |
| 174 | - .getCoupons, .getCouponSets, .getCouponSetsNew, .getAvailableCoupons, | 175 | + .getCoupons, .getCouponSets, .getCouponSetsNew, .getCouponFilters, .getAvailableCoupons, |
| 175 | .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getStores, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin, .getCarouselContent: | 176 | .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getStores, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin, .getCarouselContent: |
| 176 | return .POST | 177 | return .POST |
| 177 | case .getSingleCampaign, .getNetworkStatus: | 178 | case .getSingleCampaign, .getNetworkStatus: |
| ... | @@ -314,6 +315,14 @@ public enum Endpoint { | ... | @@ -314,6 +315,14 @@ public enum Endpoint { |
| 314 | "coupon": couponParams | 315 | "coupon": couponParams |
| 315 | ] | 316 | ] |
| 316 | 317 | ||
| 318 | + case .getCouponFilters(let language): | ||
| 319 | + return [ | ||
| 320 | + "coupon": [ | ||
| 321 | + "action": "get_filters", | ||
| 322 | + "language": language | ||
| 323 | + ] | ||
| 324 | + ] | ||
| 325 | + | ||
| 317 | case .getAvailableCoupons: | 326 | case .getAvailableCoupons: |
| 318 | return [ | 327 | return [ |
| 319 | "coupon": [ | 328 | "coupon": [ |
| ... | @@ -523,7 +532,7 @@ public enum Endpoint { | ... | @@ -523,7 +532,7 @@ public enum Endpoint { |
| 523 | return .standardContext | 532 | return .standardContext |
| 524 | 533 | ||
| 525 | // Authenticated Context - /oauth/{appUUID}/context | 534 | // Authenticated Context - /oauth/{appUUID}/context |
| 526 | - case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew: | 535 | + case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew, .getCouponFilters: |
| 527 | return .authenticatedContext | 536 | return .authenticatedContext |
| 528 | 537 | ||
| 529 | // Authentication - /oauth/{appUUID}/login, /oauth/{appUUID}/token | 538 | // Authentication - /oauth/{appUUID}/login, /oauth/{appUUID}/token |
| ... | @@ -565,7 +574,7 @@ public enum Endpoint { | ... | @@ -565,7 +574,7 @@ public enum Endpoint { |
| 565 | return .standard | 574 | return .standard |
| 566 | 575 | ||
| 567 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | 576 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) |
| 568 | - case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew: | 577 | + case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew, .getCouponFilters: |
| 569 | return .bearerToken | 578 | return .bearerToken |
| 570 | 579 | ||
| 571 | // Basic Authentication (loyalty headers + Authorization: Basic) | 580 | // Basic Authentication (loyalty headers + Authorization: Basic) | ... | ... |
| ... | @@ -659,6 +659,72 @@ public class RedeemedSMHistoryModel { | ... | @@ -659,6 +659,72 @@ public class RedeemedSMHistoryModel { |
| 659 | } | 659 | } |
| 660 | } | 660 | } |
| 661 | 661 | ||
| 662 | +// MARK: - Coupon Filters Models | ||
| 663 | + | ||
| 664 | +public class CouponFiltersDataModel { | ||
| 665 | + private var offer_categories: [CouponOfferCategoryModel]? | ||
| 666 | + private var regions: [String]? | ||
| 667 | + | ||
| 668 | + public init(dictionary: [String: Any]) { | ||
| 669 | + if let regionsArray = dictionary["regions"] as? [Any] { | ||
| 670 | + self.regions = regionsArray.compactMap { $0 as? String } | ||
| 671 | + } else { | ||
| 672 | + self.regions = [] | ||
| 673 | + } | ||
| 674 | + | ||
| 675 | + if let categoriesArray = dictionary["offer_categories"] as? [[String: Any]] { | ||
| 676 | + var tempCategories: [CouponOfferCategoryModel] = [] | ||
| 677 | + for catDict in categoriesArray { | ||
| 678 | + tempCategories.append(CouponOfferCategoryModel(dictionary: catDict)) | ||
| 679 | + } | ||
| 680 | + self.offer_categories = tempCategories | ||
| 681 | + } else { | ||
| 682 | + self.offer_categories = [] | ||
| 683 | + } | ||
| 684 | + } | ||
| 685 | + | ||
| 686 | + public var _offer_categories: [CouponOfferCategoryModel]? { get { return self.offer_categories } } | ||
| 687 | + public var _regions: [String]? { get { return self.regions } } | ||
| 688 | +} | ||
| 689 | + | ||
| 690 | +public class CouponOfferCategoryModel { | ||
| 691 | + private var uuid: String? | ||
| 692 | + private var admin_name: String? | ||
| 693 | + private var name: String? | ||
| 694 | + private var image: String? | ||
| 695 | + private var parent: String? | ||
| 696 | + private var children: [CouponOfferCategoryModel]? | ||
| 697 | + | ||
| 698 | + public init(dictionary: [String: Any]) { | ||
| 699 | + self.uuid = dictionary["uuid"] as? String | ||
| 700 | + self.admin_name = dictionary["admin_name"] as? String | ||
| 701 | + self.name = dictionary["name"] as? String | ||
| 702 | + | ||
| 703 | + if let imgString = dictionary["image"] as? String { | ||
| 704 | + self.image = imgString.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| 705 | + } | ||
| 706 | + | ||
| 707 | + self.parent = dictionary["parent"] as? String | ||
| 708 | + | ||
| 709 | + if let childrenArray = dictionary["children"] as? [[String: Any]] { | ||
| 710 | + var tempChildren: [CouponOfferCategoryModel] = [] | ||
| 711 | + for childDict in childrenArray { | ||
| 712 | + tempChildren.append(CouponOfferCategoryModel(dictionary: childDict)) | ||
| 713 | + } | ||
| 714 | + self.children = tempChildren | ||
| 715 | + } else { | ||
| 716 | + self.children = [] | ||
| 717 | + } | ||
| 718 | + } | ||
| 719 | + | ||
| 720 | + public var _uuid: String { get { return self.uuid ?? "" } } | ||
| 721 | + public var _admin_name: String { get { return self.admin_name ?? "" } } | ||
| 722 | + public var _name: String { get { return self.name ?? "" } } | ||
| 723 | + public var _image: String { get { return self.image ?? "" } } | ||
| 724 | + public var _parent: String { get { return self.parent ?? "" } } | ||
| 725 | + public var _children: [CouponOfferCategoryModel]? { get { return self.children } } | ||
| 726 | +} | ||
| 727 | + | ||
| 662 | // MARK: - String Extension for HTML | 728 | // MARK: - String Extension for HTML |
| 663 | 729 | ||
| 664 | // extension String { | 730 | // extension String { | ... | ... |
| ... | @@ -53,6 +53,7 @@ import UIKit | ... | @@ -53,6 +53,7 @@ import UIKit |
| 53 | 53 | ||
| 54 | // Coupon sets data | 54 | // Coupon sets data |
| 55 | var couponSets: [CouponSetItemModel] = [] | 55 | var couponSets: [CouponSetItemModel] = [] |
| 56 | + var couponFilters: CouponFiltersDataModel? | ||
| 56 | 57 | ||
| 57 | // Merchants data | 58 | // Merchants data |
| 58 | var merchants: [MerchantModel] = [] | 59 | var merchants: [MerchantModel] = [] |
| ... | @@ -91,7 +92,6 @@ import UIKit | ... | @@ -91,7 +92,6 @@ import UIKit |
| 91 | // Load data | 92 | // Load data |
| 92 | loadProfile() // Load Profile | 93 | loadProfile() // Load Profile |
| 93 | loadCarouselContent() | 94 | loadCarouselContent() |
| 94 | - // loadCampaigns() // Load campaigns | ||
| 95 | loadCouponSets() // Load couponsets | 95 | loadCouponSets() // Load couponsets |
| 96 | loadCoupons() | 96 | loadCoupons() |
| 97 | 97 | ||
| ... | @@ -287,26 +287,12 @@ import UIKit | ... | @@ -287,26 +287,12 @@ import UIKit |
| 287 | // } | 287 | // } |
| 288 | 288 | ||
| 289 | private func loadCouponSets() { | 289 | private func loadCouponSets() { |
| 290 | - // Load coupon sets from WarplySDK | ||
| 291 | - // WarplySDK.shared.getCouponSets { [weak self] couponSets in | ||
| 292 | - // guard let self = self, let couponSets = couponSets else { return } | ||
| 293 | - | ||
| 294 | - // self.couponSets = couponSets | ||
| 295 | - | ||
| 296 | - // // Load merchants after getting coupon sets | ||
| 297 | - // self.loadMerchants() | ||
| 298 | - | ||
| 299 | - // } failureCallback: { [weak self] errorCode in | ||
| 300 | - // print("Failed to load coupon sets: \(errorCode)") | ||
| 301 | - // // No sections added on failure - table will be empty | ||
| 302 | - // } | ||
| 303 | - | ||
| 304 | WarplySDK.shared.getCouponSetsNew( | 290 | WarplySDK.shared.getCouponSetsNew( |
| 305 | completion: { [weak self] couponSets in | 291 | completion: { [weak self] couponSets in |
| 306 | guard let self = self, let couponSets = couponSets else { return } | 292 | guard let self = self, let couponSets = couponSets else { return } |
| 307 | 293 | ||
| 308 | self.couponSets = couponSets | 294 | self.couponSets = couponSets |
| 309 | - self?.createCouponSetsSection() | 295 | + self.loadCouponFilters() |
| 310 | 296 | ||
| 311 | }, | 297 | }, |
| 312 | failureCallback: { errorCode in | 298 | failureCallback: { errorCode in |
| ... | @@ -314,6 +300,19 @@ import UIKit | ... | @@ -314,6 +300,19 @@ import UIKit |
| 314 | } | 300 | } |
| 315 | ) | 301 | ) |
| 316 | } | 302 | } |
| 303 | + | ||
| 304 | + private func loadCouponFilters() { | ||
| 305 | + WarplySDK.shared.getCouponFilters { [weak self] couponFilters in | ||
| 306 | + guard let self = self, let couponFilters = couponFilters else { return } | ||
| 307 | + | ||
| 308 | + self.couponFilters = couponFilters | ||
| 309 | + self.createCouponSetsSection() | ||
| 310 | + | ||
| 311 | + } failureCallback: { errorCode in | ||
| 312 | + print("=== getCouponFilters Error: \(errorCode)") | ||
| 313 | + self.createCouponSetsSection() | ||
| 314 | + } | ||
| 315 | + } | ||
| 317 | 316 | ||
| 318 | // MARK: - Coupons Loading | 317 | // MARK: - Coupons Loading |
| 319 | private func loadCoupons() { | 318 | private func loadCoupons() { |
| ... | @@ -368,89 +367,60 @@ import UIKit | ... | @@ -368,89 +367,60 @@ import UIKit |
| 368 | // } | 367 | // } |
| 369 | 368 | ||
| 370 | // TODO: DELETE loadMerchants - No matching needed | 369 | // TODO: DELETE loadMerchants - No matching needed |
| 371 | - private func loadMerchants() { | 370 | + // private func loadMerchants() { |
| 372 | - // Load merchants from WarplySDK (using enhanced getMerchants method) | 371 | + // // Load merchants from WarplySDK (using enhanced getMerchants method) |
| 373 | - WarplySDK.shared.getMerchants { [weak self] merchants in | 372 | + // WarplySDK.shared.getMerchants { [weak self] merchants in |
| 374 | - guard let self = self, let merchants = merchants else { | 373 | + // guard let self = self, let merchants = merchants else { |
| 375 | - // If merchants fail to load, still create coupon sets section without filtering | 374 | + // // If merchants fail to load, still create coupon sets section without filtering |
| 376 | - self?.createCouponSetsSection() | 375 | + // self?.createCouponSetsSection() |
| 377 | - return | 376 | + // return |
| 378 | - } | 377 | + // } |
| 379 | 378 | ||
| 380 | - self.merchants = merchants | 379 | + // self.merchants = merchants |
| 381 | - print("✅ [MyRewardsViewController] Loaded \(merchants.count) merchants") | 380 | + // print("✅ [MyRewardsViewController] Loaded \(merchants.count) merchants") |
| 382 | 381 | ||
| 383 | - // Load merchant categories after merchants success | 382 | + // // Load merchant categories after merchants success |
| 384 | - self.loadMerchantCategories() | 383 | + // self.loadMerchantCategories() |
| 385 | 384 | ||
| 386 | - } failureCallback: { [weak self] errorCode in | 385 | + // } failureCallback: { [weak self] errorCode in |
| 387 | - print("Failed to load merchants: \(errorCode)") | 386 | + // print("Failed to load merchants: \(errorCode)") |
| 388 | - // If merchants fail, still show coupon sets without filtering | 387 | + // // If merchants fail, still show coupon sets without filtering |
| 389 | - self?.createCouponSetsSection() | 388 | + // self?.createCouponSetsSection() |
| 390 | - } | 389 | + // } |
| 391 | - } | 390 | + // } |
| 392 | 391 | ||
| 393 | // MARK: - Merchant Categories Loading | 392 | // MARK: - Merchant Categories Loading |
| 394 | - private func loadMerchantCategories() { | 393 | + // private func loadMerchantCategories() { |
| 395 | - // Load merchant categories from WarplySDK | 394 | + // // Load merchant categories from WarplySDK |
| 396 | - WarplySDK.shared.getMerchantCategories { [weak self] categories in | 395 | + // WarplySDK.shared.getMerchantCategories { [weak self] categories in |
| 397 | - guard let self = self, let categories = categories else { | 396 | + // guard let self = self, let categories = categories else { |
| 398 | - // If categories fail to load, still create coupon sets section without filtering | 397 | + // // If categories fail to load, still create coupon sets section without filtering |
| 399 | - self?.createCouponSetsSection() | 398 | + // self?.createCouponSetsSection() |
| 400 | - return | 399 | + // return |
| 401 | - } | 400 | + // } |
| 402 | 401 | ||
| 403 | - self.merchantCategories = categories | 402 | + // self.merchantCategories = categories |
| 404 | - print("✅ [MyRewardsViewController] Loaded \(categories.count) merchant categories") | 403 | + // print("✅ [MyRewardsViewController] Loaded \(categories.count) merchant categories") |
| 405 | 404 | ||
| 406 | - // Create coupon sets sections with category-based filtering | 405 | + // // Create coupon sets sections with category-based filtering |
| 407 | - self.createCouponSetsSection() | 406 | + // self.createCouponSetsSection() |
| 408 | 407 | ||
| 409 | - } failureCallback: { [weak self] errorCode in | 408 | + // } failureCallback: { [weak self] errorCode in |
| 410 | - print("Failed to load merchant categories: \(errorCode)") | 409 | + // print("Failed to load merchant categories: \(errorCode)") |
| 411 | - // If categories fail, still show coupon sets without filtering | 410 | + // // If categories fail, still show coupon sets without filtering |
| 412 | - self?.createCouponSetsSection() | 411 | + // self?.createCouponSetsSection() |
| 413 | - } | 412 | + // } |
| 414 | - } | 413 | + // } |
| 415 | 414 | ||
| 416 | private func createCouponSetsSection() { | 415 | private func createCouponSetsSection() { |
| 417 | - // TODO: no merchant match any more | 416 | + print("🔍 [MyRewardsViewController] Starting coupon filtering by new offer_categories:") |
| 418 | - | ||
| 419 | - print("🔍 [MyRewardsViewController] Starting coupon filtering:") | ||
| 420 | print(" - Coupon Sets: \(couponSets.count)") | 417 | print(" - Coupon Sets: \(couponSets.count)") |
| 421 | - print(" - Merchants: \(merchants.count)") | ||
| 422 | - print(" - Categories: \(merchantCategories.count)") | ||
| 423 | - | ||
| 424 | - // Check if we have all required data for filtering | ||
| 425 | - guard !couponSets.isEmpty, !merchants.isEmpty, !merchantCategories.isEmpty else { | ||
| 426 | - print("⚠️ [MyRewardsViewController] Missing data for filtering - using fallback single section") | ||
| 427 | - print(" - Coupon Sets Empty: \(couponSets.isEmpty)") | ||
| 428 | - print(" - Merchants Empty: \(merchants.isEmpty)") | ||
| 429 | - print(" - Categories Empty: \(merchantCategories.isEmpty)") | ||
| 430 | - | ||
| 431 | - // Fallback: Create single section with all coupon sets | ||
| 432 | - createSingleCouponSetsSection() | ||
| 433 | - return | ||
| 434 | - } | ||
| 435 | 418 | ||
| 436 | - // Group coupon sets by merchant category | ||
| 437 | var categorySections: [SectionModel] = [] | 419 | var categorySections: [SectionModel] = [] |
| 438 | - var processedCouponSets: Set<String> = [] // Track processed coupon sets to avoid duplicates | ||
| 439 | 420 | ||
| 440 | - // Extract promoted couponsets for "Top offers" section | 421 | + // 1. Top offers (promoted) |
| 441 | let promotedCouponSets = couponSets.filter { $0._promoted } | 422 | let promotedCouponSets = couponSets.filter { $0._promoted } |
| 442 | - print("🌟 [MyRewardsViewController] Found \(promotedCouponSets.count) promoted couponsets") | ||
| 443 | - | ||
| 444 | - // Create "Top offers" section if we have promoted couponsets | ||
| 445 | if !promotedCouponSets.isEmpty { | 423 | if !promotedCouponSets.isEmpty { |
| 446 | - // Bind merchant data to promoted couponsets | ||
| 447 | - for couponSet in promotedCouponSets { | ||
| 448 | - if let merchant = merchants.first(where: { $0._uuid == couponSet._merchant_uuid }) { | ||
| 449 | - couponSet._merchant = merchant | ||
| 450 | - print(" 🔗 Bound merchant '\(merchant._name)' to promoted coupon set '\(couponSet._name)'") | ||
| 451 | - } | ||
| 452 | - } | ||
| 453 | - | ||
| 454 | let topOffersSection = SectionModel( | 424 | let topOffersSection = SectionModel( |
| 455 | sectionType: .myRewardsHorizontalCouponsets, | 425 | sectionType: .myRewardsHorizontalCouponsets, |
| 456 | title: "Top offers", | 426 | title: "Top offers", |
| ... | @@ -461,128 +431,106 @@ import UIKit | ... | @@ -461,128 +431,106 @@ import UIKit |
| 461 | print(" ✅ Created 'Top offers' section with \(promotedCouponSets.count) items") | 431 | print(" ✅ Created 'Top offers' section with \(promotedCouponSets.count) items") |
| 462 | } | 432 | } |
| 463 | 433 | ||
| 464 | - print("🔄 [MyRewardsViewController] Processing categories for filtering...") | 434 | + // 2. Group by parent CouponFilter |
| 435 | + var groupedCouponSets: [String: [CouponSetItemModel]] = [:] | ||
| 436 | + var unmatchedCouponSets: [CouponSetItemModel] = [] | ||
| 465 | 437 | ||
| 466 | - for category in merchantCategories { | 438 | + let filtersData = self.couponFilters?._offer_categories ?? [] |
| 467 | - // Find merchants in this category | 439 | + |
| 468 | - let categoryMerchants = merchants.filter { merchant in | 440 | + for couponSet in couponSets { |
| 469 | - merchant._category_uuid == category._uuid | 441 | + let offerCategory = couponSet._offer_category |
| 442 | + | ||
| 443 | + if offerCategory.isEmpty { | ||
| 444 | + unmatchedCouponSets.append(couponSet) | ||
| 445 | + continue | ||
| 470 | } | 446 | } |
| 471 | 447 | ||
| 472 | - print(" - Category '\(category.displayName)' has \(categoryMerchants.count) merchants") | 448 | + var matchedParentName: String? = nil |
| 473 | 449 | ||
| 474 | - // Find coupon sets from merchants in this category | 450 | + for parentCategory in filtersData { |
| 475 | - let categoryCouponSets = couponSets.filter { couponSet in | 451 | + let parentName = parentCategory._name.isEmpty ? parentCategory._admin_name : parentCategory._name |
| 476 | - // Skip if already processed (avoid duplicates) | ||
| 477 | - guard !processedCouponSets.contains(couponSet._uuid) else { return false } | ||
| 478 | 452 | ||
| 479 | - // Check if this coupon set belongs to any merchant in this category | 453 | + // Check parent |
| 480 | - let belongsToCategory = categoryMerchants.contains { merchant in | 454 | + if parentCategory._uuid == offerCategory || parentCategory._admin_name == offerCategory || parentCategory._name == offerCategory { |
| 481 | - merchant._uuid == couponSet._merchant_uuid | 455 | + matchedParentName = parentName |
| 456 | + break | ||
| 482 | } | 457 | } |
| 483 | 458 | ||
| 484 | - if belongsToCategory { | 459 | + // Check children |
| 485 | - processedCouponSets.insert(couponSet._uuid) | 460 | + if let children = parentCategory._children { |
| 486 | - | 461 | + for child in children { |
| 487 | - // BIND MERCHANT DATA: Find and bind the merchant to this coupon set | 462 | + if child._uuid == offerCategory || child._admin_name == offerCategory || child._name == offerCategory { |
| 488 | - if let merchant = categoryMerchants.first(where: { $0._uuid == couponSet._merchant_uuid }) { | 463 | + matchedParentName = parentName |
| 489 | - couponSet._merchant = merchant | 464 | + break |
| 490 | - print(" 🔗 Bound merchant '\(merchant._name)' to coupon set '\(couponSet._name)'") | 465 | + } |
| 491 | } | 466 | } |
| 492 | } | 467 | } |
| 493 | 468 | ||
| 494 | - return belongsToCategory | 469 | + if matchedParentName != nil { |
| 470 | + break | ||
| 471 | + } | ||
| 495 | } | 472 | } |
| 496 | 473 | ||
| 497 | - print(" - Category '\(category.displayName)' has \(categoryCouponSets.count) coupon sets") | 474 | + if let parentName = matchedParentName { |
| 498 | - | 475 | + if groupedCouponSets[parentName] == nil { |
| 499 | - // Create section if we have coupon sets for this category | 476 | + groupedCouponSets[parentName] = [] |
| 500 | - if !categoryCouponSets.isEmpty { | 477 | + } |
| 501 | - let section = SectionModel( | 478 | + groupedCouponSets[parentName]?.append(couponSet) |
| 502 | - sectionType: .myRewardsHorizontalCouponsets, | 479 | + } else { |
| 503 | - title: category.displayName, | 480 | + unmatchedCouponSets.append(couponSet) |
| 504 | - items: categoryCouponSets, | ||
| 505 | - itemType: .couponSets | ||
| 506 | - ) | ||
| 507 | - categorySections.append(section) | ||
| 508 | - | ||
| 509 | - print(" ✅ Created section for '\(category.displayName)' with \(categoryCouponSets.count) items") | ||
| 510 | } | 481 | } |
| 511 | } | 482 | } |
| 512 | 483 | ||
| 513 | - // COMMENTED OUT: Don't show unmatched couponsets - only show categorized ones | 484 | + // 3. Create Sections & Sort |
| 514 | - /* | 485 | + var dynamicSections: [SectionModel] = [] |
| 515 | - // Handle any remaining unmatched coupon sets | 486 | + for (parentName, items) in groupedCouponSets { |
| 516 | - let unmatchedCouponSets = couponSets.filter { couponSet in | 487 | + let section = SectionModel( |
| 517 | - !processedCouponSets.contains(couponSet._uuid) | ||
| 518 | - } | ||
| 519 | - | ||
| 520 | - if !unmatchedCouponSets.isEmpty { | ||
| 521 | - print(" ⚠️ Found \(unmatchedCouponSets.count) unmatched coupon sets - adding to 'Άλλες Προσφορές' section") | ||
| 522 | - | ||
| 523 | - // BIND MERCHANT DATA for unmatched coupon sets too | ||
| 524 | - for couponSet in unmatchedCouponSets { | ||
| 525 | - if let merchant = merchants.first(where: { $0._uuid == couponSet._merchant_uuid }) { | ||
| 526 | - couponSet._merchant = merchant | ||
| 527 | - print(" 🔗 Bound merchant '\(merchant._name)' to unmatched coupon set '\(couponSet._name)'") | ||
| 528 | - } | ||
| 529 | - } | ||
| 530 | - | ||
| 531 | - let unmatchedSection = SectionModel( | ||
| 532 | sectionType: .myRewardsHorizontalCouponsets, | 488 | sectionType: .myRewardsHorizontalCouponsets, |
| 533 | - title: "Άλλες Προσφορές", // "Other Offers" | 489 | + title: parentName, |
| 534 | - items: unmatchedCouponSets, | 490 | + items: items, |
| 535 | itemType: .couponSets | 491 | itemType: .couponSets |
| 536 | ) | 492 | ) |
| 537 | - categorySections.append(unmatchedSection) | 493 | + dynamicSections.append(section) |
| 538 | } | 494 | } |
| 539 | - */ | ||
| 540 | 495 | ||
| 541 | - // Sort sections by title for consistent ordering, but keep "Top offers" at the top | 496 | + let priorityCategories = ["Αγορές", "Φαγητό και καφές"] |
| 542 | - categorySections.sort { section1, section2 in | 497 | + |
| 543 | - let title1 = section1.title ?? "" | 498 | + dynamicSections.sort { s1, s2 in |
| 544 | - let title2 = section2.title ?? "" | 499 | + let title1 = s1.title ?? "" |
| 500 | + let title2 = s2.title ?? "" | ||
| 545 | 501 | ||
| 546 | - // Put "Top offers" at the beginning | 502 | + let idx1 = priorityCategories.firstIndex(of: title1) |
| 547 | - if title1 == "Top offers" { return true } | 503 | + let idx2 = priorityCategories.firstIndex(of: title2) |
| 548 | - if title2 == "Top offers" { return false } | ||
| 549 | 504 | ||
| 550 | - // All other sections sorted alphabetically | 505 | + if let i1 = idx1, let i2 = idx2 { |
| 551 | - return title1.localizedCaseInsensitiveCompare(title2) == .orderedAscending | 506 | + return i1 < i2 |
| 552 | - } | 507 | + } else if idx1 != nil { |
| 553 | - | 508 | + return true |
| 554 | - print("✅ [MyRewardsViewController] Created \(categorySections.count) category sections:") | 509 | + } else if idx2 != nil { |
| 555 | - for section in categorySections { | 510 | + return false |
| 556 | - print(" - '\(section.title ?? "Unknown")': \(section.itemCount) coupon sets") | 511 | + } else { |
| 512 | + return title1.localizedCaseInsensitiveCompare(title2) == .orderedAscending | ||
| 513 | + } | ||
| 557 | } | 514 | } |
| 558 | 515 | ||
| 559 | - // Add category sections to main sections array | 516 | + categorySections.append(contentsOf: dynamicSections) |
| 560 | - self.sections.append(contentsOf: categorySections) | ||
| 561 | - | ||
| 562 | - // Reload table view with new sections | ||
| 563 | - DispatchQueue.main.async { | ||
| 564 | - self.tableView.reloadData() | ||
| 565 | - } | ||
| 566 | - } | ||
| 567 | - | ||
| 568 | - private func createSingleCouponSetsSection() { | ||
| 569 | - print("📦 [MyRewardsViewController] Creating single fallback coupon sets section") | ||
| 570 | 517 | ||
| 571 | - // Fallback: Single section with all coupon sets | 518 | + // 4. Other Offers |
| 572 | - if !self.couponSets.isEmpty { | 519 | + if !unmatchedCouponSets.isEmpty { |
| 573 | - let couponSetsSection = SectionModel( | 520 | + let otherSection = SectionModel( |
| 574 | sectionType: .myRewardsHorizontalCouponsets, | 521 | sectionType: .myRewardsHorizontalCouponsets, |
| 575 | - title: "Προσφορές", // "Offers" | 522 | + title: "Άλλες Προσφορές", |
| 576 | - items: self.couponSets, | 523 | + items: unmatchedCouponSets, |
| 577 | itemType: .couponSets | 524 | itemType: .couponSets |
| 578 | ) | 525 | ) |
| 579 | - self.sections.append(couponSetsSection) | 526 | + categorySections.append(otherSection) |
| 580 | - | 527 | + print(" ✅ Created 'Άλλες Προσφορές' section with \(unmatchedCouponSets.count) items") |
| 581 | - print("✅ [MyRewardsViewController] Created fallback section with \(self.couponSets.count) coupon sets") | ||
| 582 | - } else { | ||
| 583 | - print("⚠️ [MyRewardsViewController] No coupon sets available - no section created") | ||
| 584 | } | 528 | } |
| 585 | 529 | ||
| 530 | + // Add category sections to main sections array | ||
| 531 | + self.sections.append(contentsOf: categorySections) | ||
| 532 | + | ||
| 533 | + // Reload table view with new sections | ||
| 586 | DispatchQueue.main.async { | 534 | DispatchQueue.main.async { |
| 587 | self.tableView.reloadData() | 535 | self.tableView.reloadData() |
| 588 | } | 536 | } | ... | ... |
-
Please register or login to post a comment