import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart'; import 'package:lottie/lottie.dart'; import 'package:nearledaily/helper/logger.dart'; import 'package:razorpay_flutter/razorpay_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../constants/color_constants.dart'; import '../../constants/font_constants.dart'; import '../../controllers/cart_controller/cart.dart'; import '../../controllers/order_controller/create_order_controller.dart'; import '../../controllers/product/product_controller.dart'; import '../../domain/provider/tenant/get_tenant_pro.dart'; import '../../helper/distance.dart'; import '../../modules/authentication/auth.dart'; import '../../modules/orders/create_order.dart'; import '../../modules/product/product.dart'; import '../../modules/tenant/get_tenant.dart'; import '../../widgets/slider_button.dart'; import '../../widgets/text_widget.dart'; import '../home_view.dart'; import '../map_view/location.dart'; import '../orders/order_succes.dart'; import 'order_countdown_page.dart'; class CartPage extends StatefulWidget { const CartPage({super.key}); @override State createState() => _CartPageState(); } class _CartPageState extends State { DateTime? selectedDate; String? selectedTime; String? selectedTimeDisplay; TenantLocation? selectedLocation; List tenantLocations = []; // ── date helpers ────────────────────────────────────────── String _fmtDate(DateTime d) => '${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}'; String _fmtNow() { final n = DateTime.now(); return '${_fmtDate(n)} ' '${n.hour.toString().padLeft(2, '0')}:' '${n.minute.toString().padLeft(2, '0')}:' '${n.second.toString().padLeft(2, '0')}'; } // ────────────────────────────────────────────────────────── Future openPreorderPicker() async { final picked = await showDatePicker( context: context, initialDate: selectedDate ?? DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 30)), ); if (picked != null) { setState(() { selectedDate = picked; selectedTime = null; selectedTimeDisplay = null; }); } await fetchTenantLocations(); if (tenantLocations.isEmpty) return; final loc = tenantLocations.first; var slots24 = _generateTimeSlots(loc.openTime, loc.closeTime); final now = DateTime.now(); if (selectedDate != null && selectedDate!.year == now.year && selectedDate!.month == now.month && selectedDate!.day == now.day) { slots24 = slots24.where((s) { final p = s.split(':'); final t = DateTime( now.year, now.month, now.day, int.parse(p[0]), int.parse(p[1]), ); return t.isAfter(now); }).toList(); } showModalBottomSheet( backgroundColor: Colors.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (_) => Container( height: 300, padding: const EdgeInsets.all(16), child: slots24.isEmpty ? const Center(child: Text('No slots available')) : ListView.separated( itemCount: slots24.length, separatorBuilder: (_, __) => const Divider(), itemBuilder: (_, i) { final t24 = slots24[i]; final t12 = _to12Hour(t24); return ListTile( title: Text(t12), onTap: () { setState(() { selectedTime = t24; selectedTimeDisplay = t12; }); Navigator.pop(context); }, ); }, ), ), ); } Future fetchTenantLocations() async { final cartController = Get.find(); if (cartController.cartItems.isEmpty) return; int? tenantId = cartController.cartItems.first.product.tenantid; try { final provider = CustomerTenantsProvider(); final response = await provider.getTenantLocations(tenantId!); if (response != null && response.details.isNotEmpty) { setState(() => tenantLocations = response.details); } else { setState(() => tenantLocations = []); } } catch (e) { // silent } } @override void initState() { super.initState(); _loadProfile(); _setDefaultDateAndTime(); final cartCtrl = Get.find(); cartCtrl.appliedCoupon.value = ""; cartCtrl.amt.value = ""; } Future _setDefaultDateAndTime() async { selectedDate = DateTime.now(); // today await fetchTenantLocations(); if (tenantLocations.isEmpty) { setState(() => selectedTime = null); return; } final first = tenantLocations.first; final slots24 = _generateTimeSlots(first.openTime, first.closeTime); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final future24 = slots24.where((s) { final p = s.split(':'); final t = DateTime(today.year, today.month, today.day, int.parse(p[0]), int.parse(p[1])); return t.isAfter(now); }).toList(); setState(() { selectedTime = future24.isNotEmpty ? future24.first : null; selectedTimeDisplay = selectedTime != null ? _to12Hour(selectedTime!) : null; }); } List _generateTimeSlots(String openTime, String closeTime) { final fmt = RegExp(r'^(\d{1,2}):(\d{2})$'); if (!fmt.hasMatch(openTime) || !fmt.hasMatch(closeTime)) return []; final o = fmt.firstMatch(openTime)!; final c = fmt.firstMatch(closeTime)!; final openH = int.parse(o.group(1)!); final openM = int.parse(o.group(2)!); final closeH = int.parse(c.group(1)!); final closeM = int.parse(c.group(2)!); DateTime cur = DateTime(0, 0, 0, openH, openM); DateTime end = DateTime(0, 0, 0, closeH, closeM); List slots = []; while (cur.isBefore(end) || cur.isAtSameMomentAs(end)) { slots.add( '${cur.hour.toString().padLeft(2, '0')}:${cur.minute.toString().padLeft(2, '0')}'); cur = cur.add(const Duration(minutes: 60)); } return slots; } String _to12Hour(String time24) { final parts = time24.split(':'); int h = int.parse(parts[0]); final m = parts[1]; final period = h >= 12 ? 'PM' : 'AM'; if (h == 0) h = 12; if (h > 12) h -= 12; return '$h:$m $period'; } String name = '', num = '', address = '', suburb = '', city = '', doorNo = ''; Future _loadProfile() async { final prefs = await SharedPreferences.getInstance(); final id = prefs.getInt('customerId'); if (id == null) { Get.snackbar('Error', 'Customer ID not found'); return; } try { final cartCtrl = Get.find(); await cartCtrl.fetchCustomer(id); final profile = cartCtrl.customer.value; if (profile != null) { setState(() { name = '${profile.firstName} ${profile.lastName}'.trim(); num = profile.contactNo ?? ''; address = profile.address ?? ''; suburb = profile.suburb ?? ''; city = profile.city ?? ''; doorNo = profile.doorNo ?? ''; }); } } catch (e) { // silent } } @override Widget build(BuildContext context) { final cartCtrl = Get.find(); final tenant = cartCtrl.currentTenant.value; return Obx(() { final items = cartCtrl.cartItems; final dis = double.tryParse(cartCtrl.amt.value.toString()) ?? 0.0; final subtotal = items.fold( 0.0, (s, i) { final cost = i.product.productcost ?? 0; final discount = i.product.discount ?? 0; final priceAfterDiscount = cost - discount; return s + (priceAfterDiscount * i.quantity); }, ); final tax = cartCtrl.totalTax; final delivery = tenant?.tenantcharge ?? 0.0; final total = subtotal + tax + delivery - dis; return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.white, Color(0xFFF9F7FC), Color(0xFFF8F6FC), Colors.white ], ), borderRadius: BorderRadius.circular(16), ), child: SafeArea( child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( surfaceTintColor: Colors.transparent, scrolledUnderElevation: 0, animateColor: false, leading: Navigator.of(context).canPop() ? Row( children: [ IconButton( onPressed: () { Navigator.pop(context); final nav = Get.find(); nav.currentIndex.value = 0; }, icon: const Icon(Icons.arrow_back, color: Colors.black), ), ReusableTextWidget( text: "Cart", fontSize: 20, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, color: Colors.black, ), ], ) : const Padding( padding: EdgeInsets.all(15.0), child: Text( 'Cart', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black, fontSize: 20), ), ), leadingWidth: double.infinity, centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, ), body: items.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: 120, width: 120, child: Lottie.asset( 'assets/lotties/no_slots_lottie.json', fit: BoxFit.contain), ), const SizedBox(height: 20), const Padding( padding: EdgeInsets.all(6.0), child: Text( 'No slots available for today.\nPlease try changing dates to schedule orders.', textAlign: TextAlign.center, style: TextStyle( color: Colors.grey, fontWeight: FontWeight.bold), ), ), ], ), ) : SingleChildScrollView( padding: const EdgeInsets.all(12), physics: const ClampingScrollPhysics(), child: Column( children: [ SizedBox(height: 13), Container( decoration: BoxDecoration( color: Colors.white, border: Border( left: BorderSide( color: Colors.black12, width: 0.50), right: BorderSide( color: Colors.black12, width: 0.50), top: BorderSide( color: Colors.black12, width: 0.50), ), borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20), ), boxShadow: [ BoxShadow( color: ColorConstants.primaryColor .withOpacity(0.02), blurRadius: 1, spreadRadius: 0.50, offset: Offset(0, -3), ), ], ), child: Column( children: items .map((item) => _cartItemWidget(item, cartCtrl)) .toList(), ), ), Container( decoration: BoxDecoration( color: Colors.white, border: Border( left: BorderSide( color: Colors.black12, width: 0.50), right: BorderSide( color: Colors.black12, width: 0.50), bottom: BorderSide( color: Colors.black12, width: 0.50), ), boxShadow: [ BoxShadow( color: ColorConstants.primaryColor .withOpacity(0.08), blurRadius: 2, spreadRadius: 0, offset: Offset(0, 3), ), ], borderRadius: BorderRadius.only( bottomRight: Radius.circular(20), bottomLeft: Radius.circular(20), )), child: _priceSummary(subtotal, tax, delivery, total, dis)), const SizedBox(height: 35), ], ), ), ), ), ); }); } Widget _cartItemWidget(CartItem item, CartController ctrl) { final p = item.product; final double screenWidth = MediaQuery.of(context).size.width; final double imageSize = math.min(100, math.max(80, screenWidth * 0.22)); return Slidable( key: ValueKey(p.productid), endActionPane: ActionPane( motion: const DrawerMotion(), extentRatio: 0.23, children: [ SlidableAction( onPressed: (_) { ctrl.removeFromCart(p); }, backgroundColor: Colors.red, foregroundColor: Colors.white, icon: Icons.delete_outline, label: 'Delete', borderRadius: const BorderRadius.only( topRight: Radius.circular(18), bottomRight: Radius.circular(18), ), ), ], ), child: Container( margin: const EdgeInsets.symmetric(vertical: 0), padding: const EdgeInsets.all(14), decoration: const BoxDecoration(), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(16), child: SizedBox( height: imageSize, width: imageSize, child: Image.network( p.productimage ?? '', fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( color: Colors.grey[300], child: const Icon(Icons.image, size: 24), ), ), ), ), SizedBox(width: screenWidth * 0.03), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: p.productname!, color: Colors.black87, fontWeight: FontWeight.w600, fontSize: screenWidth * 0.035, fontFamily: FontConstants.fontFamily, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), ReusableTextWidget( text: p.subcategoryname!, color: Colors.black87, fontWeight: FontWeight.w500, fontSize: screenWidth * 0.034, fontFamily: FontConstants.fontFamily, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), ReusableTextWidget( text: p.productdesc!, color: Colors.black45, fontWeight: FontWeight.w500, fontSize: screenWidth * 0.030, fontFamily: FontConstants.fontFamily, maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ _qtyButton( icon: Icons.remove, onTap: () { if (item.quantity > 1) { item.quantity--; ctrl.cartItems.refresh(); } else { ctrl.removeFromCart(p); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: const [ Icon(Icons.arrow_upward, color: Colors.white, size: 18), SizedBox(width: 8), Text("Removed"), ], ), backgroundColor: Colors.red, duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), ); } }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Text( '${item.quantity}', style: TextStyle( fontSize: screenWidth * 0.038, fontWeight: FontWeight.w600, ), ), ), _qtyButton( icon: Icons.add, gradient: LinearGradient( colors: [ Colors.white, ColorConstants.primaryColor.withOpacity(0.6), ], ), onTap: () { item.quantity++; ctrl.cartItems.refresh(); }, ), ], ), const SizedBox(height: 18), Row( children: [ ReusableTextWidget( text: '₹${(p.productcost! - (p.discount ?? 0)).toInt()}', color: Colors.black87, fontWeight: FontWeight.bold, fontSize: screenWidth * 0.032, fontFamily: FontConstants.fontFamily, ), const SizedBox(width: 6), if ((p.discount ?? 0) > 0) ReusableTextWidget( text: '₹${p.productcost?.toInt() ?? 0}', color: Colors.red, fontWeight: FontWeight.w600, fontSize: screenWidth * 0.03, fontFamily: FontConstants.fontFamily, isUnderText: TextDecoration.lineThrough, ), ], ), ], ), ], ), ), ); } Widget _priceSummary( double subtotal, double tax, double delivery, double total, double dis) { final cartCtrl = Get.find(); return Column( children: [ const SizedBox(height: 5), Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(7), ), child: Column( children: [ Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () { final cartCtrl = Get.find(); cartCtrl.loadCoupons(); cartCtrl.showCouponBottomSheet(context); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), spreadRadius: 2, blurRadius: 6, offset: Offset(0, 3), ), ], ), child: Row( children: [ Obx(() { bool applied = cartCtrl.amt.value.isNotEmpty; return Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: applied ? Colors.green.shade100 : Colors.orange.shade100, shape: BoxShape.circle, ), child: Icon( applied ? Icons.check_circle : Icons.discount_outlined, size: 14, color: applied ? Colors.green : Colors.orange, ), ); }), const SizedBox(width: 10), Obx(() { return Text( cartCtrl.amt.value.isEmpty ? 'Apply a promo code' : 'Discount Applied: ₹${cartCtrl.amt.value}', style: TextStyle( fontSize: 11, color: cartCtrl.amt.value.isEmpty ? Colors.black.withOpacity(0.65) : Colors.green.shade700, fontWeight: FontWeight.bold, ), ); }), const Spacer(), Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8), decoration: BoxDecoration( color: ColorConstants.primaryColor, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: const [ ReusableTextWidget( text: "Apply", color: Colors.white, fontFamily: FontConstants.fontFamily, fontSize: 12, fontWeight: FontWeight.bold, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 1, ), SizedBox(width: 6), Icon( Icons.arrow_forward_ios, size: 14, color: Colors.white, ), ], ), ) ], ), ), ), ), SizedBox(height: 12), _priceRow('Subtotal', subtotal), _priceRow('Discount', dis), _priceRow('GST', tax), _priceRow('Other Charges', delivery), const Divider(), _priceRow('Total', total, isBold: true), const SizedBox(height: 12), SliderCheckoutButton( onConfirmed: () async { final cartCtrl = Get.find(); if (cartCtrl.cartItems.any((i) => i.quantity <= 0)) { Get.snackbar('', 'Invalid Quantity', backgroundColor: Colors.white, colorText: Colors.black); print('Invalid quantity in cart items'); return; } final orderCtrl = Get.find(); final loc = await Get.to(() => LocationPage()); if (loc != null && loc is Authentication) { final tenant = Get.find().currentTenant.value; final tenantLat = double.tryParse(tenant?.latitude ?? '0') ?? 0; final tenantLon = double.tryParse(tenant?.longitude ?? '0') ?? 0; final selLat = double.tryParse(loc.latitude.toString()) ?? 0; final selLon = double.tryParse(loc.longitude.toString()) ?? 0; final dist = calculateDistance( tenantLat, tenantLon, selLat, selLon); if (dist <= 5) { showModalBottomSheet( backgroundColor: Colors.white, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(16))), builder: (ctx) => SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ ReusableTextWidget( text: 'Confirm Order', color: Colors.black.withOpacity(0.7), fontFamily: FontConstants.fontFamily, fontSize: 18, fontWeight: FontWeight.bold, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 1, ), const SizedBox(height: 20), // ── CASH ON DELIVERY ────────────────────────── Container( padding: EdgeInsets.symmetric( horizontal: 14, vertical: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.grey.shade300, width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: GestureDetector( behavior: HitTestBehavior.opaque, // ← ADD THIS child: Row(children: [ Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.money, color: Colors.green, size: 22, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: 'Cash on Delivery', color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 15, fontWeight: FontWeight.w600, ), SizedBox(height: 4), Text( 'Pay when order arrives', style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), ), Icon( Icons.arrow_forward_ios, size: 18, color: Colors.grey.shade400, ), ]), onTap: () async { Navigator.pop(ctx); final items = cartCtrl.cartItems .map((i) => OrderItem( productid: i.product.productid, productname: i.product.productname, productdescription: '', orderqty: i.quantity, price: i.product.productcost ?.toDouble() ?? 0, unitid: int.tryParse(i .product.unitvalue .toString()) ?? 1, unitname: i.product.productunit ?.toString() .split('.') .last ?? 'unit', productsumprice: total, tax: tax, discount: dis, tenantfee: delivery, )) .toList(); final order = CreateOrder( applocationid: tenant?.applocationid, applocation: '', tenantid: cartCtrl.cartItems.first .product.tenantid, partnerid: 60, locationid: int.tryParse( cartCtrl.pastLocationId ?? ""), categoryid: cartCtrl.cartItems.first .product.categoryid, subcategoryid: cartCtrl .cartItems.first.product.subcategoryid, moduleid: 2, configid: 1, orderdate: _fmtNow(), // ✅ FIXED deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created', deliverycharge: tenant?.tenantcharge, customerid: int.tryParse( loc.customerid ?? '0'), pickupcustomer: tenant?.tenantname ?? '', pickupcontactno: tenant?.primarycontact, pickupaddress: tenant?.address, pickuplocationid: tenant?.locationid, pickupcity: tenant?.city, deliverycustomer: name, deliverycontactno: num, deliveryaddress: loc.address, deliverylocationid: loc.locationid, deliverylat: loc.latitude ?? '', deliverylong: loc.longitude ?? '', paymenttype: 42, items: items, ); print(order.toJson()); print(items.map((i) => i.toJson()).toList()); print('customer details'); // ✅ Navigate to countdown page Get.to(() => OrderCountdownPage( order: order, orderCtrl: orderCtrl, cartCtrl: cartCtrl, customerName: name, )); }, ), ), const SizedBox(height: 10), // ── PAY ONLINE (RAZORPAY) ───────────────────── Container( padding: EdgeInsets.symmetric( horizontal: 14, vertical: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.grey.shade300, width: 1.5, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: GestureDetector( behavior: HitTestBehavior.opaque, // ← ADD THIS child: Row(children: [ Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.deepPurple .withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.account_balance_wallet, color: Colors.deepPurple, size: 22, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: 'Pay Online', color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 15, fontWeight: FontWeight.w600, ), SizedBox(height: 4), Text( 'UPI / Cards / Net Banking', style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), ), Icon( Icons.arrow_forward_ios, size: 18, color: Colors.grey.shade400, ), ]), onTap: () { Navigator.pop(ctx); final Razorpay razorpay = Razorpay(); razorpay.on( Razorpay.EVENT_PAYMENT_SUCCESS, (PaymentSuccessResponse response) async { razorpay.clear(); if (!context.mounted) return; final items = cartCtrl.cartItems .map((i) => OrderItem( productid: i.product.productid, productname: i.product.productname, productdescription: '', orderqty: i.quantity, price: i.product .productcost ?.toDouble() ?? 0, unitid: int.tryParse(i .product.unitvalue .toString()) ?? 1, unitname: i.product .productunit ?.toString() .split('.') .last ?? 'unit', productsumprice: total, tax: tax, discount: dis, tenantfee: delivery, )) .toList(); final order = CreateOrder( applocationid: tenant?.applocationid, applocation: '', tenantid: cartCtrl.cartItems.first .product.tenantid, partnerid: 60, locationid: int.tryParse( cartCtrl.pastLocationId ?? ""), categoryid: cartCtrl .cartItems.first.product.categoryid, subcategoryid: cartCtrl .cartItems.first.product.subcategoryid, moduleid: 6, configid: 1, orderdate: _fmtNow(), // ✅ FIXED deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created', deliverycharge: tenant?.tenantcharge, customerid: int.tryParse( loc.customerid ?? '0'), pickupcustomer: tenant?.tenantname ?? '', pickupcontactno: tenant?.primarycontact, pickupaddress: tenant?.address, pickuplocationid: tenant?.locationid, pickupcity: tenant?.city, deliverycustomer: name, deliverycontactno: num, deliveryaddress: loc.address, deliverylocationid: loc.locationid, deliverylat: loc.latitude ?? '', deliverylong: loc.longitude ?? '', paymenttype: 2, items: items, ); await orderCtrl.createOrder( CreateOrderRequest( orders: order)); if (!context.mounted) return; if (!orderCtrl.isLoading.value) { Get.to(OrderSuccessView()); print(order); print(items); cartCtrl.clearCart(); await cartCtrl.notifyAdmin( title: 'Nearle Deals - New Order', body: 'A new order has been placed successfully by $name!'); } }); razorpay.on( Razorpay.EVENT_PAYMENT_ERROR, (PaymentFailureResponse response) { razorpay.clear(); if (!context.mounted) return; ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text(response.message ?? 'Something went wrong'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); }); razorpay.on( Razorpay.EVENT_EXTERNAL_WALLET, (ExternalWalletResponse response) { razorpay.clear(); }); razorpay.open({ 'key': 'rzp_test_pUSj1Pz4LFLb', 'amount': (total * 100).toInt(), 'name': 'Nearle Deals', 'description': 'Order Payment', 'prefill': { 'contact': num, 'name': name, }, 'external': { 'wallets': ['paytm'] }, 'theme': { 'full_screen': true, }, }); }, ), ), const SizedBox(height: 10), ListTile( title: ReusableTextWidget( text: 'Cancel', color: Colors.red, fontFamily: FontConstants.fontFamily, fontSize: 18, fontWeight: FontWeight.bold, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 1, ), onTap: () => Navigator.pop(ctx), ), ], ), ), ), ); } else { showDialog( context: Get.context!, builder: (_) => AlertDialog( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16)), title: Column( children: [ Lottie.asset('assets/lotties/location.json', height: 100, repeat: true), const SizedBox(height: 8), const Text('Not Serviceable', textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 17, color: Colors.black87)), ], ), content: const Text( 'Sorry, we don\'t serve this area yet.', textAlign: TextAlign.center, style: TextStyle( fontSize: 15, color: Colors.black54)), actionsAlignment: MainAxisAlignment.center, actions: [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10))), onPressed: () => Navigator.of(context).pop(), child: const Padding( padding: EdgeInsets.symmetric( horizontal: 20, vertical: 8), child: Text('OK', style: TextStyle( color: Colors.white, fontSize: 15)), ), ), ], ), ); } } }, ), // GestureDetector( // onTap: () async { // final cartCtrl = Get.find(); // if (cartCtrl.cartItems.any((i) => i.quantity <= 0)) { // Get.snackbar('', 'Invalid Quantity', // backgroundColor: Colors.white, // colorText: Colors.black); // // print('Invalid quantity in cart items'); // return; // } // final orderCtrl = Get.find(); // final loc = await Get.to(() => LocationPage()); // // if (loc != null && loc is Authentication) { // final tenant = // Get.find().currentTenant.value; // final tenantLat = // double.tryParse(tenant?.latitude ?? '0') ?? 0; // final tenantLon = // double.tryParse(tenant?.longitude ?? '0') ?? 0; // final selLat = // double.tryParse(loc.latitude.toString()) ?? 0; // final selLon = // double.tryParse(loc.longitude.toString()) ?? 0; // final dist = calculateDistance( // tenantLat, tenantLon, selLat, selLon); // // if (dist <= 5) { // showModalBottomSheet( // backgroundColor: Colors.white, // context: context, // shape: const RoundedRectangleBorder( // borderRadius: BorderRadius.vertical( // top: Radius.circular(16))), // builder: (ctx) => SafeArea( // child: Padding( // padding: const EdgeInsets.all(16.0), // child: Column( // mainAxisSize: MainAxisSize.min, // children: [ // ReusableTextWidget( // text: 'Confirm Order', // color: Colors.black.withOpacity(0.7), // fontFamily: FontConstants.fontFamily, // fontSize: 18, // fontWeight: FontWeight.bold, // textAlign: TextAlign.center, // overflow: TextOverflow.ellipsis, // maxLines: 1, // ), // const SizedBox(height: 20), // // // ── CASH ON DELIVERY ────────────────────────── // Container( // padding: EdgeInsets.symmetric( // horizontal: 14, vertical: 16), // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(16), // border: Border.all( // color: Colors.grey.shade300, // width: 1.5, // ), // boxShadow: [ // BoxShadow( // color: // Colors.black.withOpacity(0.04), // blurRadius: 8, // offset: Offset(0, 4), // ), // ], // ), // child: GestureDetector( // behavior: HitTestBehavior.opaque, // ← ADD THIS // child: Row(children: [ // Container( // padding: EdgeInsets.all(10), // decoration: BoxDecoration( // color: // Colors.green.withOpacity(0.1), // shape: BoxShape.circle, // ), // child: Icon( // Icons.money, // color: Colors.green, // size: 22, // ), // ), // SizedBox(width: 12), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // ReusableTextWidget( // text: 'Cash on Delivery', // color: Colors.black, // fontFamily: // FontConstants.fontFamily, // fontSize: 15, // fontWeight: FontWeight.w600, // ), // SizedBox(height: 4), // Text( // 'Pay when order arrives', // style: TextStyle( // fontSize: 12, // color: Colors.grey.shade600, // ), // ), // ], // ), // ), // Icon( // Icons.arrow_forward_ios, // size: 18, // color: Colors.grey.shade400, // ), // ]), // onTap: () async { // Navigator.pop(ctx); // // final items = cartCtrl.cartItems // .map((i) => OrderItem( // productid: // i.product.productid, // productname: // i.product.productname, // productdescription: '', // orderqty: i.quantity, // price: i.product.productcost // ?.toDouble() ?? // 0, // unitid: int.tryParse(i // .product.unitvalue // .toString()) ?? // 1, // unitname: i.product.productunit // ?.toString() // .split('.') // .last ?? // 'unit', // productsumprice: total, // tax: tax, // discount: dis, // tenantfee: delivery, // )) // .toList(); // // final order = CreateOrder( // applocationid: // tenant?.applocationid, // applocation: '', // tenantid: cartCtrl.cartItems.first // .product.tenantid, // partnerid: 60, // locationid: int.tryParse( // cartCtrl.pastLocationId ?? ""), // categoryid: cartCtrl.cartItems.first // .product.categoryid, // subcategoryid: cartCtrl // .cartItems.first.product.subcategoryid, // moduleid: 6, // configid: 1, // orderdate: _fmtNow(), // ✅ FIXED // deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created', // deliverycharge: tenant?.tenantcharge, // customerid: int.tryParse( // loc.customerid ?? '0'), // pickupcustomer: // tenant?.tenantname ?? '', // pickupcontactno: // tenant?.primarycontact, // pickupaddress: tenant?.address, // pickuplocationid: tenant?.locationid, // pickupcity: tenant?.city, // deliverycustomer: name, // deliverycontactno: num, // deliveryaddress: loc.address, // deliverylocationid: loc.locationid, // deliverylat: loc.latitude ?? '', // deliverylong: loc.longitude ?? '', // paymenttype: 42, // items: items, // ); // // // print(order.toJson()); // print(items.map((i) => i.toJson()).toList()); // print('orders'); // // ✅ Navigate to countdown page // Get.to(() => OrderCountdownPage( // order: order, // orderCtrl: orderCtrl, // cartCtrl: cartCtrl, // customerName: name, // )); // // }, // ), // ), // // const SizedBox(height: 10), // // // ── PAY ONLINE (RAZORPAY) ───────────────────── // Container( // padding: EdgeInsets.symmetric( // horizontal: 14, vertical: 16), // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(16), // border: Border.all( // color: Colors.grey.shade300, // width: 1.5, // ), // boxShadow: [ // BoxShadow( // color: // Colors.black.withOpacity(0.04), // blurRadius: 8, // offset: Offset(0, 4), // ), // ], // ), // child: GestureDetector( // behavior: HitTestBehavior.opaque, // ← ADD THIS // child: Row(children: [ // Container( // padding: EdgeInsets.all(10), // decoration: BoxDecoration( // color: Colors.deepPurple // .withOpacity(0.1), // shape: BoxShape.circle, // ), // child: Icon( // Icons.account_balance_wallet, // color: Colors.deepPurple, // size: 22, // ), // ), // SizedBox(width: 12), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // ReusableTextWidget( // text: 'Pay Online', // color: Colors.black, // fontFamily: // FontConstants.fontFamily, // fontSize: 15, // fontWeight: FontWeight.w600, // ), // SizedBox(height: 4), // Text( // 'UPI / Cards / Net Banking', // style: TextStyle( // fontSize: 12, // color: Colors.grey.shade600, // ), // ), // ], // ), // ), // Icon( // Icons.arrow_forward_ios, // size: 18, // color: Colors.grey.shade400, // ), // ]), // onTap: () { // Navigator.pop(ctx); // // final Razorpay razorpay = Razorpay(); // // razorpay.on( // Razorpay.EVENT_PAYMENT_SUCCESS, // (PaymentSuccessResponse // response) async { // razorpay.clear(); // if (!context.mounted) return; // // final items = cartCtrl.cartItems // .map((i) => OrderItem( // productid: // i.product.productid, // productname: // i.product.productname, // productdescription: '', // orderqty: i.quantity, // price: i.product // .productcost // ?.toDouble() ?? // 0, // unitid: int.tryParse(i // .product.unitvalue // .toString()) ?? // 1, // unitname: i.product // .productunit // ?.toString() // .split('.') // .last ?? // 'unit', // productsumprice: total, // tax: tax, // discount: dis, // tenantfee: delivery, // )) // .toList(); // // final order = CreateOrder( // applocationid: // tenant?.applocationid, // applocation: '', // tenantid: cartCtrl.cartItems.first // .product.tenantid, // partnerid: 60, // locationid: int.tryParse( // cartCtrl.pastLocationId ?? // ""), // categoryid: cartCtrl // .cartItems.first.product.categoryid, // subcategoryid: cartCtrl // .cartItems.first.product.subcategoryid, // moduleid: 6, // configid: 1, // orderdate: _fmtNow(), // ✅ FIXED // deliverydate: '${_fmtDate(DateTime.now())} ${TimeOfDay.now().hour.toString().padLeft(2, '0')}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}:00', orderstatus: 'created', // deliverycharge: // tenant?.tenantcharge, // customerid: int.tryParse( // loc.customerid ?? '0'), // pickupcustomer: // tenant?.tenantname ?? '', // pickupcontactno: // tenant?.primarycontact, // pickupaddress: tenant?.address, // pickuplocationid: // tenant?.locationid, // pickupcity: tenant?.city, // deliverycustomer: name, // deliverycontactno: num, // deliveryaddress: loc.address, // deliverylocationid: // loc.locationid, // deliverylat: loc.latitude ?? '', // deliverylong: loc.longitude ?? '', // paymenttype: 2, // items: items, // ); // // await orderCtrl.createOrder( // CreateOrderRequest( // orders: order)); // // if (!context.mounted) return; // // if (!orderCtrl.isLoading.value) { // Get.to(OrderSuccessView()); // print(order); // print(items); // cartCtrl.clearCart(); // await cartCtrl.notifyAdmin( // title: // 'Nearle Deals - New Order', // body: // 'A new order has been placed successfully by $name!'); // } // }); // // razorpay.on( // Razorpay.EVENT_PAYMENT_ERROR, // (PaymentFailureResponse // response) { // razorpay.clear(); // if (!context.mounted) return; // ScaffoldMessenger.of(context) // .showSnackBar( // SnackBar( // content: Text(response.message ?? // 'Something went wrong'), // backgroundColor: Colors.red, // behavior: // SnackBarBehavior.floating, // ), // ); // }); // // razorpay.on( // Razorpay.EVENT_EXTERNAL_WALLET, // (ExternalWalletResponse // response) { // razorpay.clear(); // }); // // razorpay.open({ // 'key': 'rzp_test_pUSj1Pz4LFLb', // 'amount': (total * 100).toInt(), // 'name': 'Nearle Deals', // 'description': 'Order Payment', // 'prefill': { // 'contact': num, // 'name': name, // }, // 'external': { // 'wallets': ['paytm'] // }, // 'theme': { // 'full_screen': true, // }, // }); // }, // ), // ), // // const SizedBox(height: 10), // ListTile( // title: ReusableTextWidget( // text: 'Cancel', // color: Colors.red, // fontFamily: FontConstants.fontFamily, // fontSize: 18, // fontWeight: FontWeight.bold, // textAlign: TextAlign.center, // overflow: TextOverflow.ellipsis, // maxLines: 1, // ), // onTap: () => Navigator.pop(ctx), // ), // ], // ), // ), // ), // ); // } else { // showDialog( // context: Get.context!, // builder: (_) => AlertDialog( // backgroundColor: Colors.white, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(16)), // title: Column( // children: [ // Lottie.asset('assets/lotties/location.json', // height: 100, repeat: true), // const SizedBox(height: 8), // const Text('Not Serviceable', // textAlign: TextAlign.center, // style: TextStyle( // fontWeight: FontWeight.w600, // fontSize: 17, // color: Colors.black87)), // ], // ), // content: const Text( // 'Sorry, we don\'t serve this area yet.', // textAlign: TextAlign.center, // style: TextStyle( // fontSize: 15, color: Colors.black54)), // actionsAlignment: MainAxisAlignment.center, // actions: [ // ElevatedButton( // style: ElevatedButton.styleFrom( // backgroundColor: Colors.redAccent, // shape: RoundedRectangleBorder( // borderRadius: // BorderRadius.circular(10))), // onPressed: () => // Navigator.of(context).pop(), // child: const Padding( // padding: EdgeInsets.symmetric( // horizontal: 20, vertical: 8), // child: Text('OK', // style: TextStyle( // color: Colors.white, fontSize: 15)), // ), // ), // ], // ), // ); // } // } // }, // child: Container( // width: double.infinity, // padding: const EdgeInsets.symmetric(vertical: 16), // decoration: BoxDecoration( // gradient: LinearGradient( // colors: [ // ColorConstants.primaryColor, // ColorConstants.primaryColor.withOpacity(0.8) // ], // begin: Alignment.topLeft, // end: Alignment.bottomRight, // ), // borderRadius: BorderRadius.circular(14), // ), // child: Obx(() { // final oc = Get.find(); // return oc.isLoading.value // ? const Center( // child: Text('Please wait...', // style: TextStyle( // color: Colors.white, // fontSize: 16, // fontWeight: FontWeight.w500)), // ) // : Center( // child: Text('Proceed to Checkout', // style: TextStyle( // color: Colors.white, // fontSize: 16, // fontWeight: FontWeight.w500)), // ); // }), // ), // ) ], ), ), ], ); } Widget _qtyButton({ required IconData icon, required VoidCallback onTap, Gradient? gradient, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( width: 32, height: 32, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( color: ColorConstants.primaryColor.withOpacity(0.25), ), gradient: gradient, color: gradient == null ? Colors.white : null, ), child: Icon(icon, size: 18, color: Colors.black87), ), ); } Widget _priceRow(String title, double value, {bool isBold = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: LayoutBuilder( builder: (context, constraints) { final baseWidth = 392.0; final screenWidth = MediaQuery.of(context).size.width; final scale = screenWidth / baseWidth; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontWeight: isBold ? FontWeight.w700 : FontWeight.w500, fontSize: 13 * scale, color: Colors.black87, ), ), Text( '₹${value.toStringAsFixed(2)}', style: TextStyle( fontWeight: isBold ? FontWeight.w800 : FontWeight.w600, fontSize: (isBold ? 14 : 13) * scale, color: isBold ? Colors.black : Colors.black87, ), ), ], ); }, ), ); } Widget _buildProductItem(Product product) { return Container( width: 70, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( clipBehavior: Clip.none, children: [ Container( height: 70, width: 70, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(14), image: product.productimage != null ? DecorationImage( image: NetworkImage(product.productimage!), fit: BoxFit.cover, ) : null, ), ), Positioned( bottom: -8, right: -8, child: GestureDetector( onTap: () { // TODO: Add to cart }, child: Container( height: 26, width: 26, decoration: BoxDecoration( color: Colors.green, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.25), blurRadius: 6, ), ], ), child: const Icon( Icons.add, color: Colors.white, size: 18, ), ), ), ), ], ), const SizedBox(height: 8), Text( product.productname ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), ); } }