import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart' as _dio; import 'package:lottie/lottie.dart'; import 'package:nearledaily/constants/color_constants.dart'; import '../../modules/authentication/auth.dart'; import '../../modules/product/product.dart'; import '../../modules/tenant/get_tenant.dart' hide Customer; import '../../service/dio.dart'; import '../tenant_controller /tenant_list.dart'; // New Product modules class CartController extends GetxController { var cartItems = [].obs; var pastTenantId; var pastLocationId; var currentTenant = Rxn(); final CustomDio _customDio = CustomDio(); // assuming your postData() is here RxBool showCouponAnimation = false.obs; final shake = ValueNotifier(false); void triggerCouponAnimation() { showCouponAnimation.value = true; Future.delayed(Duration(seconds: 2), () { showCouponAnimation.value = false; }); } @override void onInit() { super.onInit(); appliedCoupon.value = ""; amt.value = ""; } RxList> coupons = >[].obs; RxString appliedCoupon = ''.obs; RxString amt = ''.obs; void loadCoupons() async { try { isLoading.value = true; final tenant = currentTenant.value; final tenantid = tenant?.tenantid ?? ''; final locationid = tenant?.locationid ?? 'Unknown Store'; final url = "https://jupiter.nearle.app/live/api/v1/tenants/gettenantpromotions?tenantid=$tenantid&locationid=$locationid"; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = jsonDecode(response.body); print(url); final List details = data["details"] ?? []; coupons.value = details.map((promo) { return { "code": promo["promocode"] ?? "", "desc": promo["description"] ?? "", "amount": promo["promoamount"]?.toString() ?? "0", "start": promo["startdate"] ?? "", "end": promo["enddate"] ?? "", }; }).toList(); } } catch (e) { print("loadCoupons Error: $e"); } finally { isLoading.value = false; } } void applyCoupon(String code) { appliedCoupon.value = code; triggerCouponAnimation(); } void showCouponBottomSheet(BuildContext context) { final cartCtrl = Get.find(); cartCtrl.appliedCoupon.value = ""; cartCtrl.amt.value = ""; cartCtrl.coupons.clear(); cartCtrl.loadCoupons(); // fetch coupons again showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) { return DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.4, maxChildSize: 0.95, builder: (_, controller) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(12)), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Available Coupons", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, ), ), IconButton(onPressed: (){ Navigator.of(context).pop(); // triggerCouponAnimation(); }, icon: Icon(Icons.close)) ], ), SizedBox(height: 15), Expanded( child: Obx(() { if (cartCtrl.isLoading.value) { return Center( child: Lottie.asset( 'assets/lotties/loading.json', // path to your Lottie JSON file width: 500, height: 500, fit: BoxFit.contain, ), ); } // Filter only valid (non-expired) coupons final validCoupons = cartCtrl.coupons.where((item) { try { if (item["end"] == null || item["end"].toString().isEmpty) return true; DateTime endDate = DateTime.parse(item["end"]); DateTime today = DateTime.now(); DateTime onlyToday = DateTime(today.year, today.month, today.day); DateTime onlyEnd = DateTime(endDate.year, endDate.month, endDate.day); return !onlyEnd.isBefore(onlyToday); // keep only if not expired } catch (e) { return false; // invalid date → skip } }).toList(); if (validCoupons.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Lottie animation Lottie.asset( 'assets/lotties/nodata.json', // Replace with your Lottie file path width: 150, height: 150, fit: BoxFit.contain, ), const SizedBox(height: 16), const Text( "No coupons available", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.grey, ), ), ], ), ); } return ListView.builder( controller: controller, itemCount: validCoupons.length, itemBuilder: (_, index) { final item = validCoupons[index]; return couponTile( item["code"], item["desc"], item["amount"], start: item["start"], end: item["end"], ); }, ); }), ), ], ), ); }, ); }, ); } Widget couponTile(String code, String desc, String amount, {String? start, String? end}) { final cartCtrl = Get.find(); String formatDate(String iso) { try { DateTime dt = DateTime.parse(iso); return "${dt.day}-${dt.month}-${dt.year}"; } catch (e) { return ""; } } return Container( margin: EdgeInsets.only(bottom: 12), padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Color(0xFF662582).withOpacity(0.06), borderRadius: BorderRadius.circular(14), border: Border.all(color: Color(0xFF662582).withOpacity(0.3)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 4, offset: Offset(0, 2), ) ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icon left Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( color: Color(0xFF662582).withOpacity(0.2), shape: BoxShape.circle, ), child: Icon(Icons.discount, size: 18, color: Color(0xFF662582)), ), SizedBox(width: 12), // Text section Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( code, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: Color(0xFF662582), ), ), SizedBox(height: 4), Text( desc, style: TextStyle( fontSize: 12, color: Colors.black87, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), SizedBox(height: 6), // Expiry date Padding( padding: EdgeInsets.only(top: 4), child: Text( "Add items worth ₹100 to use this coupon", style: TextStyle( color: Colors.red, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), ], ), ), SizedBox(width: 10), // Apply Button Obx(() { double t = cartCtrl.totalCost; bool isApplied = cartCtrl.cartItems.isNotEmpty && t >= 100 && cartCtrl.appliedCoupon.value == code; double totalAmount = cartCtrl.totalCost; // your total return ValueListenableBuilder( valueListenable: shake, builder: (context, isShaking, child) { return AnimatedContainer( duration: Duration(milliseconds: 80), margin: EdgeInsets.only(left: isShaking ? 4 : 0, right: isShaking ? 4 : 0), child: Column( children: [ GestureDetector( onTap: () { if (totalAmount < 100) { // 🔥 Trigger shake animation only shake.value = true; Future.delayed(Duration(milliseconds: 300), () { shake.value = false; }); return; } // Normal Apply / Remove logic if (isApplied) { cartCtrl.appliedCoupon.value = ""; cartCtrl.amt.value = ""; } else { cartCtrl.amt.value = amount; cartCtrl.appliedCoupon.value = code; } }, child: Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: isApplied ? Colors.green : Color(0xFF662582), borderRadius: BorderRadius.circular(8), ), child: Text( isApplied ? "Remove" : "Apply", style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, ), ), ), ), // 🔥 Show small red error text (only when below ₹100) // if (totalAmount < 100) // Padding( // padding: EdgeInsets.only(top: 4), // child: Text( // "Min ₹100 required", // style: TextStyle( // color: Colors.red, // fontSize: 11, // fontWeight: FontWeight.w600, // ), // ), // ), ], ), ); }, ); }), ], ), ); } var isLoading = true.obs; var customer = Rxn(); final TenantController tenantController = Get.find(); Future fetchCustomer(int customerId) async { isLoading.value = true; try { final url = Uri.parse( 'https://fiesta.nearle.app/live/api/v1/mob/customers/getbyid/?customerid=$customerId'); final response = await http.get(url); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == true) { customer.value = Customer.fromJson(data['details']); } } else { print('Error: ${response.statusCode}'); } } catch (e) { print('Exception: $e'); } finally { isLoading.value = false; } } // 🔹 Notify Admin Function using your postData helper /// Notify Admin via API Future notifyAdmin({ String title = "Nearle deals", String body = "Test -------------------------------------------------", }) async { const String endpoint = "https://jupiter.nearle.app/live/api/v1/utils/notifyadmin"; final token = currentTenant.value?.tenanttoken?.toString() ?? ""; final Map payload = { "token": [ token ], "notification": { "title": title, "body": body, "sound": "ring", "type": "tojoin" } }; try { print("📡 Sending admin notification..."); final response = await _customDio.postData(endpoint, payload); print("📌 Tenant token: $token"); print("📌 Notification title: $title"); print("📌 Notification body: $body"); print("✅ Admin notified successfully: $response"); print("📌 Tenant token from tenantController: ${currentTenant.value?.tenanttoken}"); } catch (e) { print("❌ Error notifying admin: $e"); } } Future addToCart( Product product, { int qty = 1, String? storeName, String? storeImage, String? locationId, }) async { final currentTenantId = product.tenantid.toString(); final previousTenantId = pastTenantId?.toString(); final currentLocationId = locationId?.toString(); final previousLocationId = pastLocationId?.toString(); print("Adding product from store: $currentTenantId"); print("Past Tenant ID: $previousTenantId"); print("Current Location ID: $currentLocationId"); print("Past Location ID: $previousLocationId"); final tenant = tenantController.tenants.firstWhereOrNull( (t) => t.tenantid == int.parse(currentTenantId) ); // First item → set tenant + location if (cartItems.isEmpty) { cartItems.add(CartItem(product: product, quantity: qty)); pastTenantId = currentTenantId; pastLocationId = currentLocationId; currentTenant.value = tenant; print("✅ Tenant set for cart: ${tenant?.tenantname}"); return; } // --------- MAIN CHECK (Tenant + Location Must Match) ---------- if (previousTenantId == currentTenantId && previousLocationId == currentLocationId) { // Same tenant and same location → add item normally final index = cartItems.indexWhere( (item) => item.product.productid == product.productid); if (index >= 0) { cartItems[index].quantity += qty; cartItems.refresh(); } else { cartItems.add(CartItem(product: product, quantity: qty)); } } else { // -------- DIFFERENT TENANT or DIFFERENT LOCATION ----------- // (Your Existing Replace Cart Logic stays SAME) if (Get.isBottomSheetOpen!) Get.back(); await Future.delayed(Duration(milliseconds: 100)); bool? replace = await Get.bottomSheet( SafeArea( child: Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Replace Cart?", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), IconButton( onPressed: () => Get.back(), icon: Icon(Icons.close), ) ], ), SizedBox(height: 16), Text( "Looks like your cart has items from another store or location. Replace them?", style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Get.back(result: false), child: Text("No")), SizedBox(width: 8), ElevatedButton( onPressed: () => Get.back(result: true), style: ElevatedButton.styleFrom( backgroundColor: ColorConstants.primaryColor), child: Text("Yes", style: TextStyle(color: Colors.white)), ), ], ), SizedBox(height: 16), ], ), ), ), isDismissible: false, enableDrag: false, ); if (replace == true) { cartItems.clear(); cartItems.add(CartItem(product: product, quantity: qty)); pastTenantId = currentTenantId; pastLocationId = currentLocationId; } } } double get totalTax => cartItems.fold( 0, (sum, item) => sum + ((item.product.taxamount ?? 0) * item.quantity), ); double get totalCostWithTax => cartItems.fold( 0, (sum, item) => sum + ((item.product.productcost ?? 0) + (item.product.taxamount ?? 0)) * item.quantity, ); /// Remove product from cart void removeFromCart(Product product) { cartItems.removeWhere((item) => item.product.productid == product.productid); } void increaseQty(CartItem item) { item.quantity++; cartItems.refresh(); } void decreaseQty(CartItem item) { if (item.quantity > 1) { item.quantity--; } else { cartItems.remove(item); } cartItems.refresh(); } /// Clear cart void clearCart() => cartItems.clear(); /// Total items in cart int get totalItems => cartItems.fold(0, (sum, item) => sum + item.quantity); /// Total cost of items in cart double get totalCost => cartItems.fold(0, (sum, item) => sum + ((item.product.productcost ?? 0) * item.quantity)); } /// Cart item class class CartItem { final Product product; int quantity; CartItem({required this.product, this.quantity = 1}); }