import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../app/app_state.dart'; import '../main.dart' show rootScaffoldMessengerKey; import '../models/cart.dart'; import '../models/menu_item.dart'; import '../services/api.dart'; import '../services/order_polling_service.dart'; /// Helper class to store modifier breadcrumb paths class ModifierPath { final List names; final double price; const ModifierPath({ required this.names, required this.price, }); } class CartViewScreen extends StatefulWidget { const CartViewScreen({super.key}); @override State createState() => _CartViewScreenState(); } class _CartViewScreenState extends State { Cart? _cart; bool _isLoading = true; String? _error; Map _menuItemsById = {}; @override void initState() { super.initState(); _loadCart(); } Future _loadCart() async { setState(() { _isLoading = true; _error = null; }); try { final appState = context.read(); final cartOrderId = appState.cartOrderId; if (cartOrderId == null) { setState(() { _isLoading = false; _cart = null; }); return; } // Load cart final cart = await Api.getCart(orderId: cartOrderId); // Load menu items to get names and prices final businessId = appState.selectedBusinessId; if (businessId != null) { final menuItems = await Api.listMenuItems(businessId: businessId); _menuItemsById = {for (var item in menuItems) item.itemId: item}; } setState(() { _cart = cart; _isLoading = false; }); // Update item count in app state appState.updateCartItemCount(cart.itemCount); } catch (e) { // If cart not found (deleted or doesn't exist), clear it from app state if (e.toString().contains('not_found') || e.toString().contains('Order not found')) { final appState = context.read(); appState.clearCart(); setState(() { _cart = null; _isLoading = false; }); } else { setState(() { _error = e.toString(); _isLoading = false; }); } } } Future _removeLineItem(OrderLineItem lineItem) async { try { final appState = context.read(); final cartOrderId = appState.cartOrderId; if (cartOrderId == null) return; setState(() => _isLoading = true); // Set IsSelected=false to remove the item await Api.setLineItem( orderId: cartOrderId, parentOrderLineItemId: lineItem.parentOrderLineItemId, itemId: lineItem.itemId, isSelected: false, ); // Reload cart await _loadCart(); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } Future _updateQuantity(OrderLineItem lineItem, int newQuantity) async { if (newQuantity < 1) return; try { final appState = context.read(); final cartOrderId = appState.cartOrderId; if (cartOrderId == null) return; setState(() => _isLoading = true); await Api.setLineItem( orderId: cartOrderId, parentOrderLineItemId: lineItem.parentOrderLineItemId, itemId: lineItem.itemId, isSelected: true, quantity: newQuantity, remark: lineItem.remark, ); // Reload cart await _loadCart(); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } Future _submitOrder() async { try { final appState = context.read(); final cartOrderId = appState.cartOrderId; if (cartOrderId == null) return; setState(() => _isLoading = true); await Api.submitOrder(orderId: cartOrderId); // Set active order for polling (status 1 = submitted) appState.setActiveOrder(orderId: cartOrderId, statusId: 1); // Start polling for status updates OrderPollingService.startPolling( orderId: cartOrderId, initialStatusId: 1, onStatusUpdate: (update) { // Update app state appState.updateActiveOrderStatus(update.statusId); // Show notification using global scaffold messenger key // This works even after the cart screen is popped rootScaffoldMessengerKey.currentState?.showSnackBar( SnackBar( content: Text(update.message), backgroundColor: _getStatusColorStatic(update.statusId), duration: const Duration(seconds: 5), behavior: SnackBarBehavior.floating, ), ); }, ); // Clear cart state appState.clearCart(); if (!mounted) return; // Show success message ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Order submitted successfully! You'll receive notifications as your order is prepared."), backgroundColor: Colors.green, duration: Duration(seconds: 5), ), ); // Navigate back Navigator.of(context).pop(); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } Color _getStatusColor(int statusId) => _getStatusColorStatic(statusId); static Color _getStatusColorStatic(int statusId) { switch (statusId) { case 1: // Submitted return Colors.blue; case 2: // Preparing return Colors.orange; case 3: // Ready return Colors.green; case 4: // Completed return Colors.purple; default: return Colors.grey; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Cart"), backgroundColor: Colors.black, foregroundColor: Colors.white, ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _error != null ? Center( child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error, color: Colors.red, size: 48), const SizedBox(height: 16), Text( "Error loading cart", style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text(_error!, textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton( onPressed: _loadCart, child: const Text("Retry"), ), ], ), ), ) : _cart == null || _cart!.itemCount == 0 ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.shopping_cart_outlined, size: 80, color: Colors.grey, ), const SizedBox(height: 16), Text( "Your cart is empty", style: Theme.of(context).textTheme.titleLarge, ), ], ), ) : Column( children: [ Expanded( child: ListView( padding: const EdgeInsets.all(16), children: _buildCartItems(), ), ), _buildCartSummary(), ], ), ); } List _buildCartItems() { if (_cart == null) return []; // Group line items by root items final rootItems = _cart!.lineItems .where((item) => item.parentOrderLineItemId == 0 && !item.isDeleted) .toList(); final widgets = []; for (final rootItem in rootItems) { widgets.add(_buildRootItemCard(rootItem)); widgets.add(const SizedBox(height: 12)); } return widgets; } Widget _buildRootItemCard(OrderLineItem rootItem) { final menuItem = _menuItemsById[rootItem.itemId]; final itemName = menuItem?.name ?? "Item #${rootItem.itemId}"; print('[Cart] Building card for root item: $itemName (OrderLineItemID=${rootItem.orderLineItemId})'); print('[Cart] Total line items in cart: ${_cart!.lineItems.length}'); // Find ALL modifiers (recursively) and build breadcrumb paths for leaf items only final modifierPaths = _buildModifierPaths(rootItem.orderLineItemId); print('[Cart] Found ${modifierPaths.length} modifier paths for this root item'); return Card( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( itemName, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _confirmRemoveItem(rootItem, itemName), ), ], ), if (modifierPaths.isNotEmpty) ...[ const SizedBox(height: 8), ...modifierPaths.map((path) => _buildModifierPathRow(path)), ], const SizedBox(height: 8), Row( children: [ IconButton( icon: const Icon(Icons.remove_circle_outline), onPressed: rootItem.quantity > 1 ? () => _updateQuantity(rootItem, rootItem.quantity - 1) : null, ), Text( "${rootItem.quantity}", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(Icons.add_circle_outline), onPressed: () => _updateQuantity(rootItem, rootItem.quantity + 1), ), const Spacer(), Text( "\$${rootItem.price.toStringAsFixed(2)}", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ], ), ), ); } /// Build breadcrumb paths for all leaf modifiers List _buildModifierPaths(int rootOrderLineItemId) { final paths = []; // Get direct children of root final directChildren = _cart!.lineItems .where((item) => item.parentOrderLineItemId == rootOrderLineItemId && !item.isDeleted) .toList(); // Recursively collect leaf items with their paths void collectLeafPaths(OrderLineItem item, List currentPath) { final children = _cart!.lineItems .where((child) => child.parentOrderLineItemId == item.orderLineItemId && !child.isDeleted) .toList(); final menuItem = _menuItemsById[item.itemId]; final itemName = menuItem?.name ?? "Item #${item.itemId}"; if (children.isEmpty) { // This is a leaf - add its path paths.add(ModifierPath( names: [...currentPath, itemName], price: item.price, )); } else { // This has children - recurse into them for (final child in children) { collectLeafPaths(child, [...currentPath, itemName]); } } } for (final child in directChildren) { collectLeafPaths(child, []); } return paths; } Widget _buildModifierPathRow(ModifierPath path) { final displayText = path.names.join(' > '); return Padding( padding: const EdgeInsets.only(left: 16, top: 4), child: Row( children: [ const Icon(Icons.add, size: 12, color: Colors.grey), const SizedBox(width: 4), Expanded( child: Text( displayText, style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ), if (path.price > 0) Text( "+\$${path.price.toStringAsFixed(2)}", style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), ); } Widget _buildCartSummary() { if (_cart == null) return const SizedBox.shrink(); return Container( decoration: BoxDecoration( color: Colors.grey[100], boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, -2), ), ], ), padding: const EdgeInsets.all(16), child: SafeArea( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Subtotal", style: TextStyle(fontSize: 16), ), Text( "\$${_cart!.subtotal.toStringAsFixed(2)}", style: const TextStyle(fontSize: 16), ), ], ), // Only show delivery fee for delivery orders (OrderTypeID = 3) if (_cart!.deliveryFee > 0 && _cart!.orderTypeId == 3) ...[ const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Delivery Fee", style: TextStyle(fontSize: 16), ), Text( "\$${_cart!.deliveryFee.toStringAsFixed(2)}", style: const TextStyle(fontSize: 16), ), ], ), ], const Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Total", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( "\$${_cart!.total.toStringAsFixed(2)}", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _cart!.itemCount > 0 ? _submitOrder : null, style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), ), child: const Text( "Submit Order", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ), ], ), ), ); } void _confirmRemoveItem(OrderLineItem item, String itemName) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text("Remove Item"), content: Text("Remove $itemName from cart?"), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text("Cancel"), ), TextButton( onPressed: () { Navigator.of(context).pop(); _removeLineItem(item); }, style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text("Remove"), ), ], ), ); } }