Showing
4 changed files
with
351 additions
and
18 deletions
| ... | @@ -19,6 +19,43 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { | ... | @@ -19,6 +19,43 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { |
| 19 | @IBOutlet weak var expirationLabel: UILabel! | 19 | @IBOutlet weak var expirationLabel: UILabel! |
| 20 | @IBOutlet weak var logoImage: UIImageView! | 20 | @IBOutlet weak var logoImage: UIImageView! |
| 21 | 21 | ||
| 22 | + var postImageURL: String? { | ||
| 23 | + didSet { | ||
| 24 | + if let url = postImageURL { | ||
| 25 | + self.bannerImage.image = UIImage() // UIImage(named: "loading") | ||
| 26 | + | ||
| 27 | + UIImage.loadImageUsingCacheWithUrlString(url) { image in | ||
| 28 | + // set the image only when we are still displaying the content for the image we finished downloading | ||
| 29 | + if url == self.postImageURL { | ||
| 30 | + self.bannerImage.image = image | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + else { | ||
| 35 | + self.bannerImage.image = nil | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + var logoImageURL: String? { | ||
| 41 | + didSet { | ||
| 42 | + if let url = logoImageURL { | ||
| 43 | + self.logoImage.image = UIImage() // UIImage(named: "loading") | ||
| 44 | + | ||
| 45 | + UIImage.loadImageUsingCacheWithUrlString(url) { image in | ||
| 46 | + // set the image only when we are still displaying the content for the image we finished downloading | ||
| 47 | + if url == self.logoImageURL { | ||
| 48 | + self.logoImage.image = image | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + else { | ||
| 53 | + self.logoImage.image = nil | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + | ||
| 22 | public override func awakeFromNib() { | 59 | public override func awakeFromNib() { |
| 23 | super.awakeFromNib() | 60 | super.awakeFromNib() |
| 24 | // Initialization code | 61 | // Initialization code |
| ... | @@ -68,12 +105,7 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { | ... | @@ -68,12 +105,7 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { |
| 68 | 105 | ||
| 69 | func configureCell(data: CouponSetItemModel) { | 106 | func configureCell(data: CouponSetItemModel) { |
| 70 | // Use coupon set preview image | 107 | // Use coupon set preview image |
| 71 | - let imageName = data._img_preview | 108 | + self.postImageURL = data._img_preview |
| 72 | - if !imageName.isEmpty { | ||
| 73 | - bannerImage.image = UIImage(named: imageName, in: Bundle.frameworkResourceBundle, compatibleWith: nil) | ||
| 74 | - } else { | ||
| 75 | - bannerImage.image = nil | ||
| 76 | - } | ||
| 77 | 109 | ||
| 78 | // Default to not favorite for coupon sets | 110 | // Default to not favorite for coupon sets |
| 79 | favoriteImage.image = UIImage(named: "favorite_empty", in: Bundle.frameworkResourceBundle, compatibleWith: nil) | 111 | favoriteImage.image = UIImage(named: "favorite_empty", in: Bundle.frameworkResourceBundle, compatibleWith: nil) |
| ... | @@ -93,7 +125,7 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { | ... | @@ -93,7 +125,7 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { |
| 93 | case "plus_one": | 125 | case "plus_one": |
| 94 | return 0x007AFF | 126 | return 0x007AFF |
| 95 | default: | 127 | default: |
| 96 | - return 0x6C757D | 128 | + return 0xEE417D |
| 97 | } | 129 | } |
| 98 | }() | 130 | }() |
| 99 | discountView.backgroundColor = UIColor(rgb: discountColor) | 131 | discountView.backgroundColor = UIColor(rgb: discountColor) |
| ... | @@ -106,20 +138,16 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { | ... | @@ -106,20 +138,16 @@ public class MyRewardsOfferCollectionViewCell: UICollectionViewCell { |
| 106 | subtitleLabel.font = UIFont(name: "PingLCG-Regular", size: 14) | 138 | subtitleLabel.font = UIFont(name: "PingLCG-Regular", size: 14) |
| 107 | subtitleLabel.textColor = UIColor(rgb: 0x00111B) | 139 | subtitleLabel.textColor = UIColor(rgb: 0x00111B) |
| 108 | 140 | ||
| 109 | - expirationLabel.text = "έως " + data.formattedExpiration(format: "dd-MM") | 141 | + expirationLabel.text = "έως " + data.formattedEndDate(format: "dd-MM") |
| 110 | expirationLabel.font = UIFont(name: "PingLCG-Regular", size: 13) | 142 | expirationLabel.font = UIFont(name: "PingLCG-Regular", size: 13) |
| 111 | expirationLabel.textColor = UIColor(rgb: 0x00111B) | 143 | expirationLabel.textColor = UIColor(rgb: 0x00111B) |
| 112 | 144 | ||
| 113 | - // Use first image from img array if available | 145 | + // Use merchant logo from bound merchant data |
| 114 | - if let imgArray = data._img, !imgArray.isEmpty { | 146 | + if let merchant = data._merchant, !merchant._img_preview.isEmpty { |
| 115 | - let logoName = imgArray[0] | 147 | + // Use merchant's img_preview for logo |
| 116 | - if !logoName.isEmpty { | 148 | + self.logoImageURL = merchant._img_preview |
| 117 | - logoImage.image = UIImage(named: logoName, in: Bundle.frameworkResourceBundle, compatibleWith: nil) | ||
| 118 | - } else { | ||
| 119 | - logoImage.image = nil | ||
| 120 | - } | ||
| 121 | } else { | 149 | } else { |
| 122 | - logoImage.image = nil | 150 | + self.logoImageURL = nil |
| 123 | } | 151 | } |
| 124 | } | 152 | } |
| 125 | 153 | ... | ... |
| ... | @@ -78,6 +78,9 @@ public class CouponSetItemModel { | ... | @@ -78,6 +78,9 @@ public class CouponSetItemModel { |
| 78 | private var third_party_service: String? | 78 | private var third_party_service: String? |
| 79 | private var category: String? | 79 | private var category: String? |
| 80 | 80 | ||
| 81 | + // Bound merchant data for performance | ||
| 82 | + private var merchant: MerchantModel? | ||
| 83 | + | ||
| 81 | public init(dictionary: [String: Any]) { | 84 | public init(dictionary: [String: Any]) { |
| 82 | // Existing fields | 85 | // Existing fields |
| 83 | self.uuid = dictionary["uuid"] as? String? ?? "" | 86 | self.uuid = dictionary["uuid"] as? String? ?? "" |
| ... | @@ -236,6 +239,12 @@ public class CouponSetItemModel { | ... | @@ -236,6 +239,12 @@ public class CouponSetItemModel { |
| 236 | public var _third_party_service: String { get { return self.third_party_service ?? "" } } | 239 | public var _third_party_service: String { get { return self.third_party_service ?? "" } } |
| 237 | public var _category: String { get { return self.category ?? "" } } | 240 | public var _category: String { get { return self.category ?? "" } } |
| 238 | 241 | ||
| 242 | + // Bound merchant data accessor | ||
| 243 | + public var _merchant: MerchantModel? { | ||
| 244 | + get { return self.merchant } | ||
| 245 | + set(newValue) { self.merchant = newValue } | ||
| 246 | + } | ||
| 247 | + | ||
| 239 | // Formatted expiration date for display | 248 | // Formatted expiration date for display |
| 240 | public var _expiration_formatted: String { | 249 | public var _expiration_formatted: String { |
| 241 | guard let expiration = self.expiration, !expiration.isEmpty else { | 250 | guard let expiration = self.expiration, !expiration.isEmpty else { |
| ... | @@ -272,6 +281,46 @@ public class CouponSetItemModel { | ... | @@ -272,6 +281,46 @@ public class CouponSetItemModel { |
| 272 | 281 | ||
| 273 | return "" | 282 | return "" |
| 274 | } | 283 | } |
| 284 | + | ||
| 285 | + /// Format start date with custom format | ||
| 286 | + /// - Parameter format: DateFormatter format string (e.g., "dd/MM/yyyy", "MMM yyyy", "dd-MM") | ||
| 287 | + /// - Returns: Formatted date string or empty string if invalid | ||
| 288 | + public func formattedStartDate(format: String) -> String { | ||
| 289 | + guard let startDate = self.start_date, !startDate.isEmpty else { | ||
| 290 | + return "" | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + let inputFormatter = DateFormatter() | ||
| 294 | + inputFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" | ||
| 295 | + | ||
| 296 | + if let date = inputFormatter.date(from: startDate) { | ||
| 297 | + let outputFormatter = DateFormatter() | ||
| 298 | + outputFormatter.dateFormat = format | ||
| 299 | + return outputFormatter.string(from: date) | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + return "" | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + /// Format end date with custom format | ||
| 306 | + /// - Parameter format: DateFormatter format string (e.g., "dd/MM/yyyy", "MMM yyyy", "dd-MM") | ||
| 307 | + /// - Returns: Formatted date string or empty string if invalid | ||
| 308 | + public func formattedEndDate(format: String) -> String { | ||
| 309 | + guard let endDate = self.end_date, !endDate.isEmpty else { | ||
| 310 | + return "" | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + let inputFormatter = DateFormatter() | ||
| 314 | + inputFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" | ||
| 315 | + | ||
| 316 | + if let date = inputFormatter.date(from: endDate) { | ||
| 317 | + let outputFormatter = DateFormatter() | ||
| 318 | + outputFormatter.dateFormat = format | ||
| 319 | + return outputFormatter.string(from: date) | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + return "" | ||
| 323 | + } | ||
| 275 | } | 324 | } |
| 276 | 325 | ||
| 277 | public class RedeemedMerchantDetailsModel: Codable { | 326 | public class RedeemedMerchantDetailsModel: Codable { | ... | ... |
| ... | @@ -10,7 +10,8 @@ import Foundation | ... | @@ -10,7 +10,8 @@ import Foundation |
| 10 | 10 | ||
| 11 | // MARK: - Merchant Models | 11 | // MARK: - Merchant Models |
| 12 | 12 | ||
| 13 | -public class MerchantModel: Codable { | 13 | +public class MerchantModel { |
| 14 | + // Existing fields | ||
| 14 | private var address: String? | 15 | private var address: String? |
| 15 | private var id: String? | 16 | private var id: String? |
| 16 | private var store_id: String? | 17 | private var store_id: String? |
| ... | @@ -48,7 +49,35 @@ public class MerchantModel: Codable { | ... | @@ -48,7 +49,35 @@ public class MerchantModel: Codable { |
| 48 | private var show_map: Bool? | 49 | private var show_map: Bool? |
| 49 | private var eshop: Bool? | 50 | private var eshop: Bool? |
| 50 | 51 | ||
| 52 | + // NEW FIELDS - Missing from API response | ||
| 53 | + // Image array - multiple merchant images | ||
| 54 | + private var img: [String]? | ||
| 55 | + | ||
| 56 | + // Business metadata - stored as JSON string for safety | ||
| 57 | + private var merchant_metadata_raw: String? | ||
| 58 | + | ||
| 59 | + // Contact details | ||
| 60 | + private var telephones_raw: String? | ||
| 61 | + | ||
| 62 | + // Business operations | ||
| 63 | + private var working_hours: String? | ||
| 64 | + private var year_established: Int? | ||
| 65 | + private var currency: String? | ||
| 66 | + private var bank: String? | ||
| 67 | + | ||
| 68 | + // E-commerce fields | ||
| 69 | + private var min_order_price: Double? | ||
| 70 | + private var min_order_price_takeaway: Double? | ||
| 71 | + private var min_price: Double? | ||
| 72 | + private var max_price: Double? | ||
| 73 | + | ||
| 74 | + // SEO and categorization | ||
| 75 | + private var url_name: String? | ||
| 76 | + private var tags_raw: String? // Store as comma-separated string | ||
| 77 | + private var product_raw: String? // Store as JSON string | ||
| 78 | + | ||
| 51 | public init() { | 79 | public init() { |
| 80 | + // Existing fields | ||
| 52 | self.address = "" | 81 | self.address = "" |
| 53 | self.id = "" | 82 | self.id = "" |
| 54 | self.store_id = "" | 83 | self.store_id = "" |
| ... | @@ -85,9 +114,26 @@ public class MerchantModel: Codable { | ... | @@ -85,9 +114,26 @@ public class MerchantModel: Codable { |
| 85 | self.hidden = false | 114 | self.hidden = false |
| 86 | self.show_map = false | 115 | self.show_map = false |
| 87 | self.eshop = false | 116 | self.eshop = false |
| 117 | + | ||
| 118 | + // New fields - initialize with appropriate defaults | ||
| 119 | + self.img = [] | ||
| 120 | + self.merchant_metadata_raw = "" | ||
| 121 | + self.telephones_raw = "" | ||
| 122 | + self.working_hours = "" | ||
| 123 | + self.year_established = nil | ||
| 124 | + self.currency = "" | ||
| 125 | + self.bank = "" | ||
| 126 | + self.min_order_price = nil | ||
| 127 | + self.min_order_price_takeaway = nil | ||
| 128 | + self.min_price = nil | ||
| 129 | + self.max_price = nil | ||
| 130 | + self.url_name = "" | ||
| 131 | + self.tags_raw = "" | ||
| 132 | + self.product_raw = "" | ||
| 88 | } | 133 | } |
| 89 | 134 | ||
| 90 | public init(dictionary: [String: Any]) { | 135 | public init(dictionary: [String: Any]) { |
| 136 | + // Parse existing fields | ||
| 91 | self.address = dictionary["address"] as? String? ?? "" | 137 | self.address = dictionary["address"] as? String? ?? "" |
| 92 | self.id = dictionary["id"] as? String? ?? "" | 138 | self.id = dictionary["id"] as? String? ?? "" |
| 93 | self.store_id = dictionary["store_id"] as? String? ?? "" | 139 | self.store_id = dictionary["store_id"] as? String? ?? "" |
| ... | @@ -123,6 +169,7 @@ public class MerchantModel: Codable { | ... | @@ -123,6 +169,7 @@ public class MerchantModel: Codable { |
| 123 | self.default_shown = dictionary["default_shown"] as? Bool? ?? false | 169 | self.default_shown = dictionary["default_shown"] as? Bool? ?? false |
| 124 | self.hidden = dictionary["hidden"] as? Bool? ?? false | 170 | self.hidden = dictionary["hidden"] as? Bool? ?? false |
| 125 | 171 | ||
| 172 | + // Parse extra_fields | ||
| 126 | if let extra_fields = dictionary["extra_fields"] as? [String: Any] { | 173 | if let extra_fields = dictionary["extra_fields"] as? [String: Any] { |
| 127 | self.show_map = extra_fields["show_map"] as? Bool? ?? false | 174 | self.show_map = extra_fields["show_map"] as? Bool? ?? false |
| 128 | self.eshop = extra_fields["eshop"] as? Bool? ?? false | 175 | self.eshop = extra_fields["eshop"] as? Bool? ?? false |
| ... | @@ -130,6 +177,72 @@ public class MerchantModel: Codable { | ... | @@ -130,6 +177,72 @@ public class MerchantModel: Codable { |
| 130 | self.show_map = false | 177 | self.show_map = false |
| 131 | self.eshop = false | 178 | self.eshop = false |
| 132 | } | 179 | } |
| 180 | + | ||
| 181 | + // Parse NEW FIELDS - with safe type handling | ||
| 182 | + | ||
| 183 | + // Parse img array safely | ||
| 184 | + if let imgArray = dictionary["img"] as? [String] { | ||
| 185 | + self.img = imgArray | ||
| 186 | + } else { | ||
| 187 | + self.img = [] | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + // Parse merchant_metadata as JSON string for safety | ||
| 191 | + if let metadata = dictionary["merchant_metadata"] as? [String: Any] { | ||
| 192 | + do { | ||
| 193 | + let jsonData = try JSONSerialization.data(withJSONObject: metadata, options: []) | ||
| 194 | + if let jsonString = String(data: jsonData, encoding: .utf8) { | ||
| 195 | + self.merchant_metadata_raw = jsonString | ||
| 196 | + } else { | ||
| 197 | + self.merchant_metadata_raw = "" | ||
| 198 | + } | ||
| 199 | + } catch { | ||
| 200 | + self.merchant_metadata_raw = "" | ||
| 201 | + } | ||
| 202 | + } else { | ||
| 203 | + self.merchant_metadata_raw = "" | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + // Parse contact details | ||
| 207 | + self.telephones_raw = dictionary["telephones_raw"] as? String? ?? "" | ||
| 208 | + | ||
| 209 | + // Parse business operations | ||
| 210 | + self.working_hours = dictionary["working_hours"] as? String? ?? "" | ||
| 211 | + self.year_established = dictionary["year_established"] as? Int? | ||
| 212 | + self.currency = dictionary["currency"] as? String? ?? "" | ||
| 213 | + self.bank = dictionary["bank"] as? String? ?? "" | ||
| 214 | + | ||
| 215 | + // Parse e-commerce fields | ||
| 216 | + self.min_order_price = dictionary["min_order_price"] as? Double? | ||
| 217 | + self.min_order_price_takeaway = dictionary["min_order_price_takeaway"] as? Double? | ||
| 218 | + self.min_price = dictionary["min_price"] as? Double? | ||
| 219 | + self.max_price = dictionary["max_price"] as? Double? | ||
| 220 | + | ||
| 221 | + // Parse SEO and categorization | ||
| 222 | + self.url_name = dictionary["url_name"] as? String? ?? "" | ||
| 223 | + | ||
| 224 | + // Parse tags array to comma-separated string | ||
| 225 | + if let tagsArray = dictionary["tags"] as? [String] { | ||
| 226 | + self.tags_raw = tagsArray.joined(separator: ",") | ||
| 227 | + } else { | ||
| 228 | + self.tags_raw = "" | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + // Parse product object as JSON string | ||
| 232 | + if let product = dictionary["product"] as? [String: Any] { | ||
| 233 | + do { | ||
| 234 | + let jsonData = try JSONSerialization.data(withJSONObject: product, options: []) | ||
| 235 | + if let jsonString = String(data: jsonData, encoding: .utf8) { | ||
| 236 | + self.product_raw = jsonString | ||
| 237 | + } else { | ||
| 238 | + self.product_raw = "" | ||
| 239 | + } | ||
| 240 | + } catch { | ||
| 241 | + self.product_raw = "" | ||
| 242 | + } | ||
| 243 | + } else { | ||
| 244 | + self.product_raw = "" | ||
| 245 | + } | ||
| 133 | } | 246 | } |
| 134 | 247 | ||
| 135 | public var _address: String { | 248 | public var _address: String { |
| ... | @@ -311,4 +424,133 @@ public class MerchantModel: Codable { | ... | @@ -311,4 +424,133 @@ public class MerchantModel: Codable { |
| 311 | get { return self.eshop ?? false } | 424 | get { return self.eshop ?? false } |
| 312 | set(newValue) { self.eshop = newValue } | 425 | set(newValue) { self.eshop = newValue } |
| 313 | } | 426 | } |
| 427 | + | ||
| 428 | + // MARK: - NEW FIELD ACCESSORS | ||
| 429 | + | ||
| 430 | + // Image array accessor | ||
| 431 | + public var _img: [String] { | ||
| 432 | + get { return self.img ?? [] } | ||
| 433 | + set(newValue) { self.img = newValue } | ||
| 434 | + } | ||
| 435 | + | ||
| 436 | + // Business metadata accessor (raw JSON string) | ||
| 437 | + public var _merchant_metadata_raw: String { | ||
| 438 | + get { return self.merchant_metadata_raw ?? "" } | ||
| 439 | + set(newValue) { self.merchant_metadata_raw = newValue } | ||
| 440 | + } | ||
| 441 | + | ||
| 442 | + // Contact details | ||
| 443 | + public var _telephones_raw: String { | ||
| 444 | + get { return self.telephones_raw ?? "" } | ||
| 445 | + set(newValue) { self.telephones_raw = newValue } | ||
| 446 | + } | ||
| 447 | + | ||
| 448 | + // Business operations | ||
| 449 | + public var _working_hours: String { | ||
| 450 | + get { return self.working_hours ?? "" } | ||
| 451 | + set(newValue) { self.working_hours = newValue } | ||
| 452 | + } | ||
| 453 | + | ||
| 454 | + public var _year_established: Int? { | ||
| 455 | + get { return self.year_established } | ||
| 456 | + set(newValue) { self.year_established = newValue } | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + public var _currency: String { | ||
| 460 | + get { return self.currency ?? "" } | ||
| 461 | + set(newValue) { self.currency = newValue } | ||
| 462 | + } | ||
| 463 | + | ||
| 464 | + public var _bank: String { | ||
| 465 | + get { return self.bank ?? "" } | ||
| 466 | + set(newValue) { self.bank = newValue } | ||
| 467 | + } | ||
| 468 | + | ||
| 469 | + // E-commerce fields | ||
| 470 | + public var _min_order_price: Double? { | ||
| 471 | + get { return self.min_order_price } | ||
| 472 | + set(newValue) { self.min_order_price = newValue } | ||
| 473 | + } | ||
| 474 | + | ||
| 475 | + public var _min_order_price_takeaway: Double? { | ||
| 476 | + get { return self.min_order_price_takeaway } | ||
| 477 | + set(newValue) { self.min_order_price_takeaway = newValue } | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + public var _min_price: Double? { | ||
| 481 | + get { return self.min_price } | ||
| 482 | + set(newValue) { self.min_price = newValue } | ||
| 483 | + } | ||
| 484 | + | ||
| 485 | + public var _max_price: Double? { | ||
| 486 | + get { return self.max_price } | ||
| 487 | + set(newValue) { self.max_price = newValue } | ||
| 488 | + } | ||
| 489 | + | ||
| 490 | + // SEO and categorization | ||
| 491 | + public var _url_name: String { | ||
| 492 | + get { return self.url_name ?? "" } | ||
| 493 | + set(newValue) { self.url_name = newValue } | ||
| 494 | + } | ||
| 495 | + | ||
| 496 | + public var _tags_raw: String { | ||
| 497 | + get { return self.tags_raw ?? "" } | ||
| 498 | + set(newValue) { self.tags_raw = newValue } | ||
| 499 | + } | ||
| 500 | + | ||
| 501 | + public var _product_raw: String { | ||
| 502 | + get { return self.product_raw ?? "" } | ||
| 503 | + set(newValue) { self.product_raw = newValue } | ||
| 504 | + } | ||
| 505 | + | ||
| 506 | + // MARK: - COMPUTED PROPERTIES FOR COMPLEX DATA | ||
| 507 | + | ||
| 508 | + // Computed property to get metadata as dictionary | ||
| 509 | + public var merchantMetadata: [String: Any]? { | ||
| 510 | + guard let rawString = merchant_metadata_raw, | ||
| 511 | + !rawString.isEmpty, | ||
| 512 | + let data = rawString.data(using: .utf8), | ||
| 513 | + let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { | ||
| 514 | + return nil | ||
| 515 | + } | ||
| 516 | + return dict | ||
| 517 | + } | ||
| 518 | + | ||
| 519 | + // Computed property to get tags as array | ||
| 520 | + public var tagsArray: [String] { | ||
| 521 | + guard let rawTags = tags_raw, !rawTags.isEmpty else { return [] } | ||
| 522 | + return rawTags.components(separatedBy: ",").filter { !$0.isEmpty } | ||
| 523 | + } | ||
| 524 | + | ||
| 525 | + // Computed property to get product info as dictionary | ||
| 526 | + public var productInfo: [String: Any]? { | ||
| 527 | + guard let rawString = product_raw, | ||
| 528 | + !rawString.isEmpty, | ||
| 529 | + let data = rawString.data(using: .utf8), | ||
| 530 | + let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { | ||
| 531 | + return nil | ||
| 532 | + } | ||
| 533 | + return dict | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + // Computed property to check if merchant has progress completed | ||
| 537 | + public var hasProgressCompleted: Bool { | ||
| 538 | + return merchantMetadata?["progressCompleted"] as? Bool ?? false | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + // Computed property to get main image URL (first in img array or fallback to img_preview) | ||
| 542 | + public var mainImageURL: String { | ||
| 543 | + if let imgArray = img, !imgArray.isEmpty { | ||
| 544 | + return imgArray.first ?? _img_preview | ||
| 545 | + } | ||
| 546 | + return _img_preview | ||
| 547 | + } | ||
| 548 | + | ||
| 549 | + // Computed property to get pin image URL (second in img array if available) | ||
| 550 | + public var pinImageURL: String? { | ||
| 551 | + if let imgArray = img, imgArray.count > 1 { | ||
| 552 | + return imgArray[1] | ||
| 553 | + } | ||
| 554 | + return nil | ||
| 555 | + } | ||
| 314 | } | 556 | } | ... | ... |
| ... | @@ -256,6 +256,12 @@ import UIKit | ... | @@ -256,6 +256,12 @@ import UIKit |
| 256 | 256 | ||
| 257 | if belongsToCategory { | 257 | if belongsToCategory { |
| 258 | processedCouponSets.insert(couponSet._uuid) | 258 | processedCouponSets.insert(couponSet._uuid) |
| 259 | + | ||
| 260 | + // BIND MERCHANT DATA: Find and bind the merchant to this coupon set | ||
| 261 | + if let merchant = categoryMerchants.first(where: { $0._uuid == couponSet._merchant_uuid }) { | ||
| 262 | + couponSet._merchant = merchant | ||
| 263 | + print(" 🔗 Bound merchant '\(merchant._name)' to coupon set '\(couponSet._name)'") | ||
| 264 | + } | ||
| 259 | } | 265 | } |
| 260 | 266 | ||
| 261 | return belongsToCategory | 267 | return belongsToCategory |
| ... | @@ -285,6 +291,14 @@ import UIKit | ... | @@ -285,6 +291,14 @@ import UIKit |
| 285 | if !unmatchedCouponSets.isEmpty { | 291 | if !unmatchedCouponSets.isEmpty { |
| 286 | print(" ⚠️ Found \(unmatchedCouponSets.count) unmatched coupon sets - adding to 'Άλλες Προσφορές' section") | 292 | print(" ⚠️ Found \(unmatchedCouponSets.count) unmatched coupon sets - adding to 'Άλλες Προσφορές' section") |
| 287 | 293 | ||
| 294 | + // BIND MERCHANT DATA for unmatched coupon sets too | ||
| 295 | + for couponSet in unmatchedCouponSets { | ||
| 296 | + if let merchant = merchants.first(where: { $0._uuid == couponSet._merchant_uuid }) { | ||
| 297 | + couponSet._merchant = merchant | ||
| 298 | + print(" 🔗 Bound merchant '\(merchant._name)' to unmatched coupon set '\(couponSet._name)'") | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 288 | let unmatchedSection = SectionModel( | 302 | let unmatchedSection = SectionModel( |
| 289 | sectionType: .myRewardsHorizontalCouponsets, | 303 | sectionType: .myRewardsHorizontalCouponsets, |
| 290 | title: "Άλλες Προσφορές", // "Other Offers" | 304 | title: "Άλλες Προσφορές", // "Other Offers" | ... | ... |
-
Please register or login to post a comment