import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:pdf/pdf.dart'; import 'package:printing/printing.dart'; import 'package:provider/provider.dart'; import 'package:sqflite/sqflite.dart'; import 'package:yoshi_papas_app/models/pedido_producto_model.dart'; import 'package:yoshi_papas_app/views/pedido/pedido_ticket.dart'; import 'package:yoshi_papas_app/widgets/widgets_components.dart'; import 'package:yoshi_papas_app/models/item_carrito_model.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 _PedidoFormState extends State { final _busqueda = TextEditingController(text: ''); CategoriaProductoViewModel cvm = CategoriaProductoViewModel(); ProductoViewModel pvm = ProductoViewModel(); PedidoViewModel pedvm = PedidoViewModel(); bool _isLoading = false; CategoriaProducto? categoriaSeleccionada; List categorias = []; List productos = []; List carrito = []; Producto? _productoActual; bool _estadoBusqueda = false; Pedido? pedidoActual; ScrollController _gridViewController = ScrollController(); final _searchController = TextEditingController(); 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(); cargarProductosIniciales(); } _onSearchChanged(String value) { if (value.isEmpty) { cargarProductosIniciales(); } else { Provider.of(context, listen: false) .fetchLocalByName(nombre: value); } } void _finalizeOrder() async { if (carrito.isEmpty) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Pedido vacío'), content: const Text( 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.'), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actions: [ TextButton( style: TextButton.styleFrom( foregroundColor: Colors.black, backgroundColor: AppTheme.primary // Color del texto ), onPressed: () { Navigator.of(context).pop(); // Cerrar el diálogo }, child: const Text('Aceptar'), ), ], ); }, ); return; } else { // Suponiendo que `pedidoActual` es tu pedido actual que ya tiene un ID asignado int? pedidoId = pedidoActual?.id; if (pedidoId != null) { Navigator.of(context) .pop(); // Regresar a la pantalla anterior o cerrar diálogo } } await _promptForCustomerName(); } Future _promptForCustomerName() async { TextEditingController nombreController = TextEditingController(); TextEditingController comentarioController = TextEditingController(); String errorMessage = ''; // Variable para almacenar el mensaje de error bool? shouldSave = await showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( // Usar StatefulBuilder para actualizar el contenido del diálogo builder: (context, setState) { return AlertDialog( title: const Text('Finalizar Pedido'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nombreController, decoration: InputDecoration( hintText: "Nombre del Cliente", errorText: errorMessage.isEmpty ? null : errorMessage, // Mostrar el mensaje de error aquí ), autofocus: true, onChanged: (value) { if (value.trim().isEmpty) { setState(() => errorMessage = "El nombre del cliente es obligatorio."); } else { setState(() => errorMessage = ''); } }, ), TextField( controller: comentarioController, decoration: const InputDecoration( hintText: "Comentarios (opcional)"), maxLines: 3, ), ], ), actions: [ TextButton( child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(false); // Return false on cancel }, ), TextButton( child: const Text('Guardar'), onPressed: () { if (nombreController.text.trim().isEmpty) { // Actualizar el mensaje de error si el campo está vacío setState(() => errorMessage = "El nombre del cliente es obligatorio."); return; // No cerrar el diálogo } Navigator.of(context).pop(true); // Return true on save }, ), ], ); }, ); }, ); if (shouldSave ?? false) { // Preparar y guardar el pedido sólo si el usuario no canceló el diálogo prepararPedidoActual(nombreController.text, comentarioController.text); } } void prepararPedidoActual(String nombreCliente, String comentarios) async { DateTime now = DateTime.now(); String formattedDate = DateFormat('yyyy-MM-dd kk:mm:ss').format(now); // Crear una instancia de Pedido Pedido nuevoPedido = Pedido( peticion: formattedDate, nombreCliente: nombreCliente, comentarios: comentarios, estatus: "NUEVO", ); // Preparar la lista de PedidoProducto a partir del carrito List listaPedidoProducto = carrito.map((item) { return PedidoProducto( idProducto: item.producto.id, producto: item.producto, // Esto debe tener todos los detalles del producto. costoUnitario: item.producto.precio, cantidad: item.cantidad, comentario: comentarios, ); }).toList(); // Asignar la lista de productos al pedido nuevoPedido.productos = listaPedidoProducto; // Usar el ViewModel para guardar el pedido en la base de datos local bool result = await Provider.of(context, listen: false) .guardarPedidoLocal(pedido: nuevoPedido); if (result) { printTickets(nuevoPedido); Navigator.of(context).pop(); // Volver a la pantalla anterior. } else { print("Error al guardar el pedido"); } } 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. } Future cargarProductosIniciales() async { setState(() => _isLoading = true); // Llama al método para obtener todos los productos desde el ViewModel local await Provider.of(context, listen: false) .fetchLocalAll(); // Obtiene la lista de productos del ViewModel productos = Provider.of(context, listen: false).productos; setState(() => _isLoading = false); } @override void dispose() { _gridViewController.dispose(); _searchController.dispose(); super.dispose(); } Future cargarCategoriasIniciales() async { setState(() => _isLoading = true); await Provider.of(context, listen: false) .fetchLocalAll(); categorias = Provider.of(context, listen: false) .categoriaProductos; if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; cargarProductosPorCategoria(categoriaSeleccionada!.id); } setState(() => _isLoading = false); if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; } } void cargarProductosPorCategoria(int categoriaId) async { setState(() => _isLoading = true); await Provider.of(context, listen: false) .fetchLocalByID(idCategoria: categoriaId); productos = Provider.of(context, listen: false).productos; // Restablece la posición de desplazamiento _gridViewController.jumpTo(_gridViewController.position.minScrollExtent); setState(() => _isLoading = false); if (_gridViewController.hasClients) { _gridViewController.jumpTo(0.0); } } void agregarAlCarrito(Producto producto) { setState(() { var existente = carrito.firstWhereOrNull((item) => item.producto.id == producto.id); if (existente != null) { existente.cantidad++; } else { carrito.add(ItemCarrito(producto: producto, cantidad: 1)); } }); } 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, {List? toppings}) { // Revisa si hay un producto en el carrito con los mismos toppings var existingIndex = carrito.indexWhere((item) => item.producto.id == producto.id && listEquals(item.toppings, toppings)); if (existingIndex != -1) { carrito[existingIndex].cantidad++; } else { carrito.add(ItemCarrito( producto: producto, cantidad: 1, toppings: toppings ?? [])); } setState(() {}); } void finalizeCustomization() { if (_productoActual != null) { addToCart(_productoActual!); setState(() { _productoActual = null; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Crear Pedido"), ), body: Row( children: [ Flexible( flex: 3, child: _buildCartSection(), ), SizedBox(width: 35), Flexible(flex: 7, child: _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]; // Concatena los nombres de los toppings en una sola línea String toppingsList = item.toppings.map((topping) => topping.nombre).join(', '); 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)), ], ), ), Padding( padding: const EdgeInsets.only(left: 16.0, top: 4.0), child: Text(toppingsList, // Usa la lista concatenada aquí style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14.0)), ), 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: _finalizeOrder, 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() { return Column( children: [ // _buildSearchBar(), const SizedBox(height: 10), _buildCategoryButtons(), const SizedBox(height: 15), Expanded( child: Consumer(builder: (context, model, child) { productos = model.productos; return GridView.builder( controller: _gridViewController, key: ValueKey(categoriaSeleccionada?.id ?? 0), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 3 / 2, ), itemCount: productos.length, itemBuilder: (context, index) { final producto = productos[index]; if (producto.idCategoria != categoriaSeleccionada?.id) { return Container(); } return Card( child: InkWell( onTap: () => agregarAlCarrito(producto), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.fastfood, size: 80), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( producto.nombre ?? '', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 8), Text( '\$${producto.precio}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF008000), ), ), ], ), ), ); }, ); }), ) ], ); } Widget _buildCategoryButtons() { return Container( height: 50, // Define una altura fija para los botones child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: categorias.length, itemBuilder: (context, index) { final categoria = categorias[index]; bool isSelected = categoriaSeleccionada?.id == categoria.id; return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: ElevatedButton( onPressed: () { cargarProductosPorCategoria(categoria.id); setState(() { categoriaSeleccionada = categoria; }); }, style: ElevatedButton.styleFrom( primary: isSelected ? AppTheme.primary : Colors.grey, onPrimary: Colors.white, ), child: Text(categoria.nombre!), ), ); }, ), ); } Widget _buildSearchBar() { return Padding( padding: const EdgeInsets.all(8.0), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Buscar producto...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), ), ), onChanged: _onSearchChanged, // Usar el método de búsqueda aquí ), ); } }