import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:yoshi_papas_app/widgets/widgets_components.dart'; import '../../themes/themes.dart'; // Asegúrate de tener este archivo con los temas correctos import '../../models/models.dart'; // Tus modelos de datos import '../../viewmodels/viewmodels.dart'; // Tus ViewModels import 'package:collection/collection.dart'; class PedidoForm extends StatefulWidget { @override _PedidoFormState createState() => _PedidoFormState(); } class ItemCarrito { Producto producto; int cantidad; Map? customizaciones; ItemCarrito( {required this.producto, this.cantidad = 1, this.customizaciones}); } class _PedidoFormState extends State { CategoriaProductoViewModel cvm = CategoriaProductoViewModel(); ProductoViewModel pvm = ProductoViewModel(); bool _isLoading = false; ScrollController horizontalScrollController = ScrollController(); CategoriaProducto? categoriaSeleccionada; List categorias = []; List productos = []; List carrito = []; TopingViewModel _topingViewModel = TopingViewModel(); TopingCategoriaViewModel _categoriaTopingViewModel = TopingCategoriaViewModel(); List _topingsDisponibles = []; // Lista de topings disponibles Producto? _productoActual; // Producto actual seleccionado para customización bool isCustomizingProduct = false; Map? currentProductForCustomization; String? selectedBase; List selectedSauce = []; List selectedDressing = []; List selectedToppings = []; Map baseOptions = {}; Map sauceOptions = {}; Map dressingOptions = {}; Map toppingOptions = {}; List categoriasDeTopings = []; // Añade esta línea TopingCategoria? _currentTopingCategoriaSeleccionada; // Añade esta línea List _topingsFiltrados = []; // Añade esta línea Map> selectedToppingsByCategory = {}; double calcularTotalPedido() { double total = 0; for (var item in carrito) { total += double.parse(item.producto.precio!) * item.cantidad; } return total; } @override void initState() { super.initState(); cargarCategoriasIniciales(); _cargarCategoriasDeTopings(); _cargarTopingsDisponibles(); } void _cargarCategoriasDeTopings() async { var categoriasObtenidas = await _categoriaTopingViewModel.fetchRegistros(); setState(() { categoriasDeTopings = categoriasObtenidas; }); } // Función para cargar topings disponibles void _cargarTopingsDisponibles() async { _topingsDisponibles = await _topingViewModel.fetchRegistros(); var categorias = await _categoriaTopingViewModel.fetchRegistros(); var categoriaBase = categorias.firstWhereOrNull( (cat) => cat.nombre == 'Base', ); if (categoriaBase != null) { var topingsBase = _topingsDisponibles.where((toping) { return toping.idCategoria == categoriaBase.id; }).toList(); setState(() { baseOptions = { for (var toping in topingsBase) toping.nombre!: false, }; }); } } void _onTopingCategoriaSelected(TopingCategoria categoriaSeleccionada) { setState(() { _currentTopingCategoriaSeleccionada = categoriaSeleccionada; // Filtrar y actualizar la lista de toppings para mostrar basada en la categoría seleccionada _topingsFiltrados = _topingsDisponibles .where((t) => t.idCategoria == categoriaSeleccionada.id) .toList(); }); } void _onTopingSelected(Toping toping) { print("Topping seleccionado antes de setState: ${toping.nombre}"); setState(() { // Obtiene la lista actual de toppings seleccionados para la categoría final currentSelections = selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] ?? []; if (currentSelections.contains(toping.nombre)) { // Si el topping ya está seleccionado, lo deselecciona currentSelections.remove(toping.nombre); } else { // Comprobar las reglas de selección if (_currentTopingCategoriaSeleccionada!.nombre == 'Base' || currentSelections.length < 2) { // Si es la categoría 'Base' o hay menos de 2 selecciones, selecciona el topping if (_currentTopingCategoriaSeleccionada!.nombre == 'Base') { // Si es 'Base', solo permite una selección currentSelections.clear(); } currentSelections.add(toping.nombre!); } // De lo contrario, no se permite la selección y puedes mostrar un mensaje o algo similar } // Actualiza el mapa con las nuevas selecciones selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] = currentSelections; }); print( "Toppings seleccionados después de setState: ${selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id]}"); } @override void dispose() { horizontalScrollController.dispose(); super.dispose(); } Future cargarCategoriasIniciales() async { setState(() => _isLoading = true); var categoriasObtenidas = await cvm.getCategoriaProducto(); setState(() { categorias = categoriasObtenidas; _isLoading = false; // Selecciona la primera categoría por defecto si hay categorías disponibles if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; seleccionarCategoria(categoriaSeleccionada!); } }); } Future seleccionarCategoria(CategoriaProducto categoria) async { if (!mounted) return; setState(() { _isLoading = true; categoriaSeleccionada = categoria; }); productos = await pvm.fetchRegistros(categoriaProducto: categoria); if (!mounted) return; setState(() => _isLoading = false); } void agregarAlCarrito(Producto producto) { setState(() { var indice = carrito.indexWhere((item) => item.producto.id == producto.id); if (indice != -1) { carrito[indice].cantidad++; } else { // Se agrega el nuevo producto al carrito con cantidad inicial de 1 carrito.add(ItemCarrito(producto: producto)); } }); } void quitarDelCarrito(Producto producto) { setState(() { // Comienza con setState por la misma razón var indice = carrito.indexWhere((item) => item.producto.id == producto.id); if (indice != -1) { if (carrito[indice].cantidad > 1) { carrito[indice].cantidad--; } else { carrito.removeAt(indice); } } }); } void addToCart(Producto producto, {Map? customizations}) { var existingIndex = carrito.indexWhere((item) => item.producto.id == producto.id); if (existingIndex != -1) { carrito[existingIndex].cantidad++; } else { carrito.add( ItemCarrito(producto: producto, customizaciones: customizations)); } setState(() {}); } void customizeProduct(Producto producto) { // Asumimos que este producto requiere customización setState(() { _productoActual = producto; isCustomizingProduct = true; if (categoriasDeTopings.isNotEmpty) { // Selecciona la primera categoría de toppings automáticamente _currentTopingCategoriaSeleccionada = categoriasDeTopings.first; // Filtra los toppings basado en la primera categoría _topingsFiltrados = _topingsDisponibles .where((toping) => toping.idCategoria == _currentTopingCategoriaSeleccionada!.id) .toList(); } }); } void finalizeCustomization() { if (_productoActual != null) { Map customizations = { 'base': selectedBase, 'sauce': selectedSauce, 'dressing': selectedDressing, 'toppings': selectedToppings, }; addToCart(_productoActual!, customizations: customizations); setState(() { isCustomizingProduct = false; // Salir del modo de customización _productoActual = null; // Resetear el producto actual // También resetear las opciones de customización aquí si es necesario }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Crear Pedido"), ), body: Row( children: [ Flexible( flex: 3, child: _buildCartSection(), ), SizedBox(width: 35), Flexible( flex: 7, child: isCustomizingProduct ? buildCustomizationOptions() : _buildProductsSection(), ), ], ), ); } Widget _buildTotalSection() { double total = calcularTotalPedido(); // Aquí llamarías a la función calcularTotalPedido return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Total', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), Text("\$${total.toStringAsFixed(2)}", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), ], ), ); } Widget _buildCartSection() { return Card( margin: const EdgeInsets.all(8.0), child: Column( children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Producto', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), Text('Cantidad', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), ], ), ), Expanded( child: ListView.builder( itemCount: carrito.length, itemBuilder: (context, index) { final item = carrito[index]; return ListTile( title: Text(item.producto.nombre!), subtitle: Text('\$${item.producto.precio}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => eliminarProductoDelCarrito(index), ), IconButton( icon: const Icon(Icons.remove), onPressed: () => quitarDelCarrito(item.producto), ), const SizedBox(width: 5), Text( '${item.cantidad}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(width: 5), IconButton( icon: const Icon(Icons.add), onPressed: () => agregarAlCarrito(item.producto), ), // Botón para eliminar el producto del carrito ], ), ); }, ), ), const Divider( thickness: 5, ), _buildTotalSection(), const SizedBox(height: 25), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () { // Aquí implementarías la lógica para finalizar el pedido }, style: ElevatedButton.styleFrom( primary: AppTheme.primary, onPrimary: AppTheme.secondary, textStyle: const TextStyle(fontSize: 22), fixedSize: const Size(250, 50)), child: const Text('Finalizar Pedido'), ), ), ], ), ); } void eliminarProductoDelCarrito(int index) { setState(() { carrito.removeAt(index); }); } Widget _buildProductsSection() { // Asumiendo que tienes un método para obtener los productos según la categoría seleccionada return Column( children: [ _buildCategoryButtons(), const SizedBox(height: 10), Expanded( child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // Número de columnas childAspectRatio: 3 / 2, // Proporción de cada tarjeta ), itemCount: productos.length, itemBuilder: (context, index) { final producto = productos[index]; return Card( child: InkWell( onTap: () { // Modifica esta parte para verificar si el producto necesita customización if (producto.toping == 1) { // Asume que `toping` es 1 si necesita customización customizeProduct(producto); } else { // Si no requiere customización, agrega directamente al carrito agregarAlCarrito(producto); } }, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.fastfood, size: 80), const SizedBox(height: 8), Padding( padding: EdgeInsets.fromLTRB(30, 0, 30, 0), child: Text( producto.nombre ?? '', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), )), const SizedBox(height: 8), Text( '\$${producto.precio}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF008000)), ), ], ), ), ); }, ), ), ], ); } Widget _buildCategoryButtons() { return Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 50, // Altura para los botones child: Scrollbar( controller: horizontalScrollController, thumbVisibility: true, thickness: 5.0, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: categorias.length, controller: horizontalScrollController, itemBuilder: (context, index) { final categoria = categorias[index]; bool esSeleccionada = categoriaSeleccionada?.id == categoria.id; return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: ElevatedButton( onPressed: () => seleccionarCategoria(categoria), style: ElevatedButton.styleFrom( primary: esSeleccionada ? const Color(0xFFFF848F) : Colors.white, onPrimary: Colors.black, textStyle: const TextStyle( fontWeight: FontWeight.bold, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(18.0), side: BorderSide( color: esSeleccionada ? Colors.black : Colors.grey), ), ), child: Text(categoria.nombre ?? 'Sin nombre'), ), ); }, ), ), ), ], ); } Widget buildCustomizationOptions() { return Container( margin: const EdgeInsets.all(8.0), child: Card( child: Column( children: [ Expanded( child: Row( children: [ Expanded( flex: 4, child: ListView.builder( itemCount: categoriasDeTopings.length, itemBuilder: (context, index) { final categoria = categoriasDeTopings[index]; return ListTile( title: Text( categoria.nombre ?? 'N/A', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), selected: _currentTopingCategoriaSeleccionada == categoria, trailing: Image.network( categoria.mediaTopingCategoria[0].media!.ruta!, width: 80, ), onTap: () { setState(() { _currentTopingCategoriaSeleccionada = categoria; _topingsFiltrados = _topingsDisponibles .where((t) => t.idCategoria == categoria.id) .toList(); }); }, ); }, ), ), const VerticalDivider(width: 0), Expanded( flex: 6, child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, ), itemCount: _topingsFiltrados.length, itemBuilder: (context, index) { final toping = _topingsFiltrados[index]; return Card( child: InkWell( onTap: () => _onTopingSelected(toping), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Aquí iría la imagen del toping, asegúrate de manejar errores de red. Image.network( toping.mediaToping[0].media!.ruta!, width: 80, ), Text(toping.nombre!), ], ), ), ); }, ), ), ], ), ), Container( padding: EdgeInsets.symmetric(vertical: 8.0), alignment: Alignment.centerRight, // El botón para finalizar la personalización child: _buildConfirmButton(), ), ], ), ), ); } Widget _buildBaseOptions() { return GridView.count( crossAxisCount: 3, children: baseOptions.keys.map((String key) { bool isSelected = baseOptions[key] ?? false; // Determina si está seleccionado return GestureDetector( onTap: () { setState(() { baseOptions.keys.forEach( (k) => baseOptions[k] = false); // Desmarca todos primero baseOptions[key] = true; // Marca el seleccionado }); }, child: Container( decoration: BoxDecoration( color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4), border: Border.all(color: isSelected ? Colors.red : Colors.grey), ), child: Center( child: Text(key, style: TextStyle( color: isSelected ? Colors.white : Colors.black)), ), ), ); }).toList(), ); } Widget _buildSauceOptions() { return GridView.count( crossAxisCount: 3, children: sauceOptions.keys.map((String key) { bool isSelected = sauceOptions[key] ?? false; return GestureDetector( onTap: () { int selectedCount = sauceOptions.values.where((b) => b).length; setState(() { // Si la salsa ya está seleccionada, la deselecciona. if (isSelected) { sauceOptions[key] = false; } else { // Si se están seleccionando menos de 2 salsas, permite esta selección. if (selectedCount < 2) { sauceOptions[key] = true; } else { // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada. final firstSelected = sauceOptions.keys.firstWhere( (k) => sauceOptions[k]!, orElse: () => '', ); if (firstSelected.isNotEmpty) { sauceOptions[firstSelected] = false; sauceOptions[key] = true; } } } }); }, child: Container( decoration: BoxDecoration( color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4), border: Border.all(color: Colors.grey), ), child: Center( child: Text(key, style: TextStyle( color: isSelected ? Colors.white : Colors.black)), ), ), ); }).toList(), ); } Widget _buildDressingOptions() { return GridView.count( crossAxisCount: 3, children: dressingOptions.keys.map((String key) { bool isSelected = dressingOptions[key] ?? false; return GestureDetector( onTap: () { int selectedCount = dressingOptions.values.where((b) => b).length; setState(() { // Si la salsa ya está seleccionada, la deselecciona. if (isSelected) { dressingOptions[key] = false; } else { // Si se están seleccionando menos de 2 salsas, permite esta selección. if (selectedCount < 2) { dressingOptions[key] = true; } else { // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada. final firstSelected = dressingOptions.keys.firstWhere( (k) => dressingOptions[k]!, orElse: () => '', ); if (firstSelected.isNotEmpty) { dressingOptions[firstSelected] = false; dressingOptions[key] = true; } } } }); }, child: Container( decoration: BoxDecoration( color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4), border: Border.all(color: Colors.grey), ), child: Center( child: Text(key, style: TextStyle( color: isSelected ? Colors.white : Colors.black)), ), ), ); }).toList(), ); } Widget _buildToppingsOptions() { return GridView.builder( key: ValueKey(_topingsFiltrados.length), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, ), itemCount: _topingsFiltrados.length, itemBuilder: (context, index) { final toping = _topingsFiltrados[index]; // Verifica si el topping está seleccionado consultando el mapa final isSelected = selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] ?.contains(toping.nombre) ?? false; return Card( color: isSelected ? Colors.red : Colors.white, // Cambia el color si está seleccionado child: InkWell( onTap: () => _onTopingSelected(toping), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.network( toping.mediaToping[0].media!.ruta!, fit: BoxFit.cover, width: 80, height: 80, ), Text( toping.nombre!, style: TextStyle( color: isSelected ? Colors.white : Colors.black, ), ), ], ), ), ); }, ); } Widget _buildToppingOptionsForCategory(int idCategoria) { var toppingsDeCategoria = _topingsDisponibles.where((t) => t.idCategoria == idCategoria).toList(); return GridView.count( crossAxisCount: 3, children: toppingsDeCategoria.map((topping) { return GestureDetector( onTap: () { // Lógica para seleccionar/deseleccionar topping }, child: Container( // Construye tu widget de topping aquí, podrías incluir la imagen y el nombre ), ); }).toList(), ); } Widget _buildConfirmButton() { return ElevatedButton( style: ElevatedButton.styleFrom( primary: AppTheme.primary, // Color del botón onPrimary: Colors.black, textStyle: const TextStyle(fontSize: 22), padding: const EdgeInsets.fromLTRB(80, 20, 80, 20)), onPressed: () { finalizeCustomization(); // Este método creará el pedido personalizado }, child: Text('Confirmar Pedido'), ); } }