import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:yoshi_papas_app/widgets/widgets_components.dart'; import '../../themes/themes.dart'; import '../../models/models.dart'; import '../../viewmodels/viewmodels.dart'; import 'package:collection/collection.dart'; import '../../widgets/widgets.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 { final _busqueda = TextEditingController(text: ''); 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 = {}; List categoriasDeTopings = []; // Añade esta línea TopingCategoria? _currentTopingCategoriaSeleccionada; // Añade esta línea List _topingsFiltrados = []; // Añade esta línea Map> selectedToppingsByCategory = {}; Map toppingOptions = {}; List _selectedToppings = []; bool _estadoBusqueda = false; 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 _limpiarBusqueda() async { setState(() { _busqueda.text = ''; _estadoBusqueda = false; }); // Carga nuevamente las categorías y productos iniciales. await cargarCategoriasIniciales(); // Opcionalmente, puedes llamar a una función específica para cargar productos iniciales si tienes una. } void _cargarCategoriasDeTopings() async { var categoriasObtenidas = await _categoriaTopingViewModel.fetchRegistros(); setState(() { categoriasDeTopings = categoriasObtenidas; }); } // Función para cargar topings disponibles void _cargarTopingsDisponibles() async { _topingsDisponibles = await _topingViewModel.fetchRegistros(limitee: -1); 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 _onToppingSelected(Toping topping) { // Verifica si el topping ya está seleccionado bool isSelected = _selectedToppings.contains(topping.id); final List currentSelectedToppings = selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] ?? []; if (isSelected) { // Si ya está seleccionado, lo deseleccionamos setState(() { _selectedToppings.remove(topping.id); currentSelectedToppings.remove(topping.id); selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] = List.from(currentSelectedToppings); }); } else { // Si no está seleccionado, verifica las restricciones antes de seleccionar if (_currentTopingCategoriaSeleccionada!.id == 1 && currentSelectedToppings.isNotEmpty) { // Si es la categoría 'Base', solo permite una selección setState(() { currentSelectedToppings.clear(); currentSelectedToppings.add(topping.id); _selectedToppings.clear(); _selectedToppings.add(topping.id); }); } else { // Si la lista ya tiene dos toppings, quita el primero y agrega el nuevo if (currentSelectedToppings.length >= 2) { setState(() { _selectedToppings.remove(currentSelectedToppings.first); currentSelectedToppings .removeAt(0); // Elimina el primer topping seleccionado }); } // Ahora agrega el nuevo topping seleccionado setState(() { currentSelectedToppings.add(topping.id); _selectedToppings.add(topping.id); }); } selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] = List.from(currentSelectedToppings); } } Future realizarBusqueda() async { setState(() { _isLoading = true; _estadoBusqueda = true; // Establecer estado de búsqueda }); _busqueda.text = _busqueda.text.trim(); // Buscar productos basados en el texto de búsqueda //var productosResultantes = await pvm.fetchRegistros(q: _busqueda.text); _busqueda.text = _busqueda.text.trim(); await Provider.of(context, listen: false) .setIsLoading(true); await Provider.of(context, listen: false) .setBusqueda(_busqueda.text); var productosResultantes = await Provider.of(context, listen: false) .fetchRegistros(limitee: -1); await Provider.of(context, listen: false) .setBusqueda(""); await Provider.of(context, listen: false) .setIsLoading(false); // Extraer los IDs únicos de las categorías de los productos resultantes var idsCategoriasUnicos = productosResultantes.map((p) => p.idCategoria).toSet(); // Filtrar las categorías localmente para incluir solo las que coinciden con los IDs extraídos var categoriasFiltradas = cvm.categoriaProductos .where((categoria) => idsCategoriasUnicos.contains(categoria.id)) .toList(); setState(() { _isLoading = false; productos = productosResultantes; categorias = categoriasFiltradas; categoriaSeleccionada = categorias.isNotEmpty ? categorias.first : null; }); } @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, limitee: -1); 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}) { // Revisa si hay un producto en el carrito con las mismas customizaciones var existingIndex = carrito.indexWhere((item) => item.producto.id == producto.id && mapEquals(item.customizaciones, customizations)); if (existingIndex != -1) { carrito[existingIndex].cantidad++; } else { carrito.add(ItemCarrito( producto: producto, cantidad: 1, 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) { // Prepara el diccionario de customizaciones con listas vacías para cada categoría Map> customizations = { 'BASE': [], 'SALSA': [], 'ADEREZO': [], 'TOPPINGS': [], }; // Itera sobre los toppings seleccionados, clasificándolos por categoría selectedToppingsByCategory.forEach((categoryId, toppingIds) { var categoryName = _categoriaTopingViewModel.getById(categoryId)?.clave; toppingIds.forEach((toppingId) { var topping = _topingViewModel.getById(toppingId); if (topping != null && categoryName != null && customizations.containsKey(categoryName.toUpperCase())) { customizations[categoryName.toUpperCase()]!.add(topping.nombre!); } }); }); // Asegúrate de que la customización final se almacene como deseas, por ejemplo, como un solo mapa si es necesario Map finalCustomizations = {}; customizations.forEach((key, value) { finalCustomizations[key] = value.join(', '); }); addToCart(_productoActual!, customizations: finalCustomizations); // Limpia el estado para la siguiente personalización setState(() { isCustomizingProduct = false; _productoActual = null; _currentTopingCategoriaSeleccionada = null; // Agrega esta línea _topingsFiltrados.clear(); // Agrega esta línea selectedToppingsByCategory.clear(); // Agrega esta línea _selectedToppings.clear(); // Ya existe, asegúrate de que se llama }); } } @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)), ], ), ); } List _buildToppingList(Map? customizations) { List list = []; customizations?.forEach((category, toppingsAsString) { if (toppingsAsString is String && toppingsAsString.isNotEmpty) { // Divide la string por comas para obtener los nombres individuales de los toppings List toppingNames = toppingsAsString.split(', '); for (var toppingName in toppingNames) { list.add(ListTile( title: Text(toppingName), subtitle: Text(category), // Muestra la categoría como subtítulo )); } } }); return list; } 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]; // Crear una lista de Widgets para las customizaciones. List customizationWidgets = []; item.customizaciones?.forEach((category, toppingsAsString) { if (toppingsAsString is String && toppingsAsString.isNotEmpty) { // Si hay más de un topping en la cadena, sepáralos y muéstralos en la misma línea customizationWidgets.add( Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(left: 16.0, top: 4.0), child: Text( '- $category: $toppingsAsString', // Cambio clave aquí style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14.0, )), ), ), ); } }); return Column( children: [ ListTile( title: Text( item.producto.nombre!, style: const TextStyle(fontWeight: FontWeight.w600), ), subtitle: Text( '\$${item.producto.precio}', style: const TextStyle( fontWeight: FontWeight.bold, color: Color(0xFF008000)), ), 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), ), ], ), ), // Coloca aquí las customizaciones directamente. ...customizationWidgets, Divider(), // Opcional: Un divisor visual entre los elementos. ], ); }, ), ), 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: [ Row( children: [ Expanded( flex: 8, child: AppTextField( prefixIcon: const Icon(Icons.search), etiqueta: 'Búsqueda por nombre...', controller: _busqueda, hintText: 'Búsqueda por nombre...', suffixIcon: IconButton( icon: Icon( Icons.cancel, color: AppTheme.primary, ), onPressed: () { _limpiarBusqueda(); }, ), ), ), const SizedBox(width: 10), Expanded( flex: 2, child: botonElevated( accion: () async { realizarBusqueda(); }, ), ), ], ), const SizedBox(height: 5), _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]; String nombreCategoria = categorias .firstWhere( (cat) => cat.id == producto.idCategoria, orElse: () => CategoriaProducto(nombre: "Desconocida"), ) .nombre!; 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)), ), _estadoBusqueda ? Text( nombreCategoria, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ) : Container() ], ), ), ); }, ), ), ], ); } 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: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), selected: _currentTopingCategoriaSeleccionada == categoria, trailing: Image.network( categoria.mediaTopingCategoria[0].media!.ruta!, width: 80, loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ); }, errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { // Aquí puedes devolver un widget como un ícono o imagen local para indicar el error return const Icon( Icons.menu_book, size: 30, ); }, ), 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, // Usando _topingsFiltrados directamente itemBuilder: (context, index) { final topping = _topingsFiltrados[index]; bool isSelected = _selectedToppings.contains(topping.id); return Card( color: isSelected ? const Color(0xFFFC8178) : Colors.white, child: InkWell( onTap: () => _onToppingSelected(topping), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.network( topping.imagenes[0].media!.ruta!, width: 80, loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress .expectedTotalBytes != null ? loadingProgress .cumulativeBytesLoaded / loadingProgress .expectedTotalBytes! : null, ), ); }, errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { // Aquí puedes devolver un widget como un ícono o imagen local para indicar el error return const Icon( Icons.lunch_dining, size: 60, ); }, ), Text(topping.nombre!, style: TextStyle( color: isSelected ? Colors.white : Colors.black)), ], ), ), ); }, ), ), ], ), ), Container( padding: EdgeInsets.symmetric(vertical: 8.0), alignment: Alignment.centerRight, // El botón para finalizar la personalización child: _buildConfirmButton(), ), ], ), ), ); } 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'), ); } }