|
@@ -1,21 +1,14 @@
|
|
|
import 'dart:async';
|
|
|
-
|
|
|
+import 'dart:io';
|
|
|
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 {
|
|
@@ -38,20 +31,37 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
Pedido? pedidoActual;
|
|
|
ScrollController _gridViewController = ScrollController();
|
|
|
final _searchController = TextEditingController();
|
|
|
+ final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
|
|
|
|
|
|
double calcularTotalPedido() {
|
|
|
double total = 0;
|
|
|
+
|
|
|
for (var item in carrito) {
|
|
|
total += double.parse(item.producto.precio!) * item.cantidad;
|
|
|
+ item.selectedToppings.forEach((categoryId, selectedToppingIds) {
|
|
|
+ for (int toppingId in selectedToppingIds) {
|
|
|
+ Producto? topping = item.selectableToppings[categoryId]?.firstWhere(
|
|
|
+ (topping) => topping.id == toppingId,
|
|
|
+ orElse: () => Producto(precio: '0'));
|
|
|
+ if (topping != null) {
|
|
|
+ total += (double.tryParse(topping.precio!) ?? 0) * item.cantidad;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
+
|
|
|
return total;
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
- cargarCategoriasIniciales();
|
|
|
- cargarProductosIniciales();
|
|
|
+ cargarCategoriasIniciales().then((_) {
|
|
|
+ if (categorias.isNotEmpty) {
|
|
|
+ categoriaSeleccionada = categorias.first;
|
|
|
+ cargarProductosPorCategoria(categoriaSeleccionada!.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
_onSearchChanged(String value) {
|
|
@@ -69,21 +79,23 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
context: context,
|
|
|
builder: (BuildContext context) {
|
|
|
return AlertDialog(
|
|
|
- title: const Text('Pedido vacío'),
|
|
|
+ title: const Text('Pedido vacío',
|
|
|
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
|
|
|
content: const Text(
|
|
|
- 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.'),
|
|
|
+ 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.',
|
|
|
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)),
|
|
|
shape:
|
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
|
actions: <Widget>[
|
|
|
TextButton(
|
|
|
style: TextButton.styleFrom(
|
|
|
- foregroundColor: Colors.black,
|
|
|
- backgroundColor: AppTheme.primary // Color del texto
|
|
|
- ),
|
|
|
+ padding: EdgeInsets.fromLTRB(30, 20, 30, 20),
|
|
|
+ foregroundColor: AppTheme.quaternary,
|
|
|
+ backgroundColor: AppTheme.tertiary),
|
|
|
onPressed: () {
|
|
|
- Navigator.of(context).pop(); // Cerrar el diálogo
|
|
|
+ Navigator.of(context).pop();
|
|
|
},
|
|
|
- child: const Text('Aceptar'),
|
|
|
+ child: const Text('Aceptar', style: TextStyle(fontSize: 18)),
|
|
|
),
|
|
|
],
|
|
|
);
|
|
@@ -91,11 +103,9 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
);
|
|
|
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
|
|
|
+ Navigator.of(context).pop();
|
|
|
}
|
|
|
}
|
|
|
await _promptForCustomerName();
|
|
@@ -104,28 +114,26 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
Future<void> _promptForCustomerName() async {
|
|
|
TextEditingController nombreController = TextEditingController();
|
|
|
TextEditingController comentarioController = TextEditingController();
|
|
|
- String errorMessage = ''; // Variable para almacenar el mensaje de error
|
|
|
-
|
|
|
+ String errorMessage = '';
|
|
|
bool? shouldSave = await showDialog<bool>(
|
|
|
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'),
|
|
|
+ actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
|
|
|
+ title: const Text(
|
|
|
+ 'Finalizar Pedido',
|
|
|
+ style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
|
|
|
+ ),
|
|
|
content: Column(
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
children: [
|
|
|
- TextField(
|
|
|
+ AppTextField(
|
|
|
controller: nombreController,
|
|
|
- decoration: InputDecoration(
|
|
|
- hintText: "Nombre del Cliente",
|
|
|
- errorText: errorMessage.isEmpty
|
|
|
- ? null
|
|
|
- : errorMessage, // Mostrar el mensaje de error aquí
|
|
|
- ),
|
|
|
- autofocus: true,
|
|
|
+ etiqueta: 'Nombre',
|
|
|
+ hintText: "Nombre del Cliente",
|
|
|
+ errorText: errorMessage.isEmpty ? null : errorMessage,
|
|
|
onChanged: (value) {
|
|
|
if (value.trim().isEmpty) {
|
|
|
setState(() => errorMessage =
|
|
@@ -135,33 +143,55 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
}
|
|
|
},
|
|
|
),
|
|
|
- TextField(
|
|
|
+ const SizedBox(height: 10),
|
|
|
+ AppTextField(
|
|
|
controller: comentarioController,
|
|
|
- decoration: const InputDecoration(
|
|
|
- hintText: "Comentarios (opcional)"),
|
|
|
+ etiqueta: 'Comentarios (opcional)',
|
|
|
+ hintText: 'Comentarios',
|
|
|
maxLines: 3,
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
actions: <Widget>[
|
|
|
- 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
|
|
|
- },
|
|
|
- ),
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ TextButton(
|
|
|
+ child: const Text('Cancelar',
|
|
|
+ style: TextStyle(fontSize: 18)),
|
|
|
+ onPressed: () {
|
|
|
+ Navigator.of(context).pop(false);
|
|
|
+ },
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(30, 20, 30, 20)),
|
|
|
+ backgroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.primary),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.secondary)),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 100),
|
|
|
+ TextButton(
|
|
|
+ child:
|
|
|
+ const Text('Guardar', style: TextStyle(fontSize: 18)),
|
|
|
+ onPressed: () {
|
|
|
+ if (nombreController.text.trim().isEmpty) {
|
|
|
+ setState(() => errorMessage =
|
|
|
+ "El nombre del cliente es obligatorio.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Navigator.of(context).pop(true);
|
|
|
+ },
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(30, 20, 30, 20)),
|
|
|
+ backgroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.tertiary),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.quaternary)),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ )
|
|
|
],
|
|
|
);
|
|
|
},
|
|
@@ -170,45 +200,53 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
);
|
|
|
|
|
|
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);
|
|
|
+ String formattedDate = DateFormat('dd-MM-yyyy kk:mm:ss').format(now);
|
|
|
+
|
|
|
+ double totalPedido = calcularTotalPedido();
|
|
|
|
|
|
- // Crear una instancia de Pedido
|
|
|
Pedido nuevoPedido = Pedido(
|
|
|
peticion: formattedDate,
|
|
|
nombreCliente: nombreCliente,
|
|
|
comentarios: comentarios,
|
|
|
estatus: "NUEVO",
|
|
|
+ totalPedido: totalPedido,
|
|
|
);
|
|
|
|
|
|
- // Preparar la lista de PedidoProducto a partir del carrito
|
|
|
List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
|
|
|
+ List<PedidoProductoTopping> selectedToppings = [];
|
|
|
+
|
|
|
+ item.selectedToppings.forEach((categoryId, selectedToppingIds) {
|
|
|
+ for (int toppingId in selectedToppingIds) {
|
|
|
+ selectedToppings.add(PedidoProductoTopping(
|
|
|
+ idTopping: toppingId,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
return PedidoProducto(
|
|
|
idProducto: item.producto.id,
|
|
|
- producto:
|
|
|
- item.producto, // Esto debe tener todos los detalles del producto.
|
|
|
+ producto: item.producto,
|
|
|
costoUnitario: item.producto.precio,
|
|
|
cantidad: item.cantidad,
|
|
|
comentario: comentarios,
|
|
|
+ toppings: selectedToppings,
|
|
|
);
|
|
|
}).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<PedidoViewModel>(context, listen: false)
|
|
|
.guardarPedidoLocal(pedido: nuevoPedido);
|
|
|
|
|
|
if (result) {
|
|
|
- printTickets(nuevoPedido);
|
|
|
- Navigator.of(context).pop(); // Volver a la pantalla anterior.
|
|
|
+ imprimirTicketsJuntos(nuevoPedido);
|
|
|
+ Navigator.of(context).pop();
|
|
|
} else {
|
|
|
print("Error al guardar el pedido");
|
|
|
}
|
|
@@ -219,17 +257,13 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
_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<void> cargarProductosIniciales() async {
|
|
|
setState(() => _isLoading = true);
|
|
|
- // Llama al método para obtener todos los productos desde el ViewModel local
|
|
|
await Provider.of<ProductoViewModel>(context, listen: false)
|
|
|
.fetchLocalAll();
|
|
|
- // Obtiene la lista de productos del ViewModel
|
|
|
productos =
|
|
|
Provider.of<ProductoViewModel>(context, listen: false).productos;
|
|
|
setState(() => _isLoading = false);
|
|
@@ -261,32 +295,53 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
void cargarProductosPorCategoria(int categoriaId) async {
|
|
|
setState(() => _isLoading = true);
|
|
|
await Provider.of<ProductoViewModel>(context, listen: false)
|
|
|
- .fetchLocalByID(idCategoria: categoriaId);
|
|
|
+ .fetchAllByCategory(categoriaId);
|
|
|
productos =
|
|
|
Provider.of<ProductoViewModel>(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) {
|
|
|
+ void agregarAlCarrito(Producto producto) async {
|
|
|
+ var existente = carrito.firstWhereOrNull((item) =>
|
|
|
+ item.producto.id == producto.id &&
|
|
|
+ mapEquals(item.selectedToppings, {}));
|
|
|
+
|
|
|
+ if (existente != null) {
|
|
|
+ setState(() {
|
|
|
existente.cantidad++;
|
|
|
- } else {
|
|
|
- carrito.add(ItemCarrito(producto: producto, cantidad: 1));
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ Map<int, List<Producto>> toppingsSeleccionables =
|
|
|
+ await obtenerToppingsSeleccionables(producto);
|
|
|
+
|
|
|
+ setState(() {
|
|
|
+ carrito.add(ItemCarrito(
|
|
|
+ producto: producto,
|
|
|
+ cantidad: 1,
|
|
|
+ selectableToppings: toppingsSeleccionables,
|
|
|
+ ));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<Map<int, List<Producto>>> obtenerToppingsSeleccionables(
|
|
|
+ Producto producto) async {
|
|
|
+ Map<int, List<Producto>> toppingsSeleccionables = {};
|
|
|
+ final toppingCategories =
|
|
|
+ await pvm.obtenerToppingsPorProducto(producto.id!);
|
|
|
+
|
|
|
+ for (int toppingId in toppingCategories) {
|
|
|
+ Producto? topping = await pvm.obtenerProductoPorId(toppingId);
|
|
|
+ if (topping != null && topping.idCategoria != null) {
|
|
|
+ toppingsSeleccionables[topping.idCategoria!] ??= [];
|
|
|
+ toppingsSeleccionables[topping.idCategoria]!.add(topping);
|
|
|
}
|
|
|
- });
|
|
|
+ }
|
|
|
+ return toppingsSeleccionables;
|
|
|
}
|
|
|
|
|
|
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) {
|
|
@@ -299,18 +354,20 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- void addToCart(Producto producto, {List<Toping>? toppings}) {
|
|
|
- // Revisa si hay un producto en el carrito con los mismos toppings
|
|
|
+ void addToCart(Producto producto) {
|
|
|
var existingIndex = carrito.indexWhere((item) =>
|
|
|
- item.producto.id == producto.id && listEquals(item.toppings, toppings));
|
|
|
+ item.producto.id == producto.id &&
|
|
|
+ mapEquals(item.selectedToppings, {}));
|
|
|
|
|
|
if (existingIndex != -1) {
|
|
|
- carrito[existingIndex].cantidad++;
|
|
|
+ setState(() {
|
|
|
+ carrito[existingIndex].cantidad++;
|
|
|
+ });
|
|
|
} else {
|
|
|
- carrito.add(ItemCarrito(
|
|
|
- producto: producto, cantidad: 1, toppings: toppings ?? []));
|
|
|
+ setState(() {
|
|
|
+ carrito.add(ItemCarrito(producto: producto, cantidad: 1));
|
|
|
+ });
|
|
|
}
|
|
|
- setState(() {});
|
|
|
}
|
|
|
|
|
|
void finalizeCustomization() {
|
|
@@ -326,8 +383,9 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
Widget build(BuildContext context) {
|
|
|
return Scaffold(
|
|
|
appBar: AppBar(
|
|
|
- title: const Text("Crear Pedido"),
|
|
|
- ),
|
|
|
+ title:
|
|
|
+ Text("Crear Pedido", style: TextStyle(color: AppTheme.secondary)),
|
|
|
+ iconTheme: IconThemeData(color: AppTheme.secondary)),
|
|
|
body: Row(
|
|
|
children: [
|
|
|
Flexible(
|
|
@@ -342,8 +400,8 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
}
|
|
|
|
|
|
Widget _buildTotalSection() {
|
|
|
- double total =
|
|
|
- calcularTotalPedido(); // Aquí llamarías a la función calcularTotalPedido
|
|
|
+ double total = calcularTotalPedido();
|
|
|
+ String formattedTotal = _numberFormat.format(total);
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
child: Row(
|
|
@@ -351,7 +409,7 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
children: [
|
|
|
const Text('Total',
|
|
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
|
|
- Text("\$${total.toStringAsFixed(2)}",
|
|
|
+ Text("\$$formattedTotal",
|
|
|
style:
|
|
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
|
|
],
|
|
@@ -359,23 +417,6 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- List<Widget> _buildToppingList(Map<String, dynamic>? customizations) {
|
|
|
- List<Widget> 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<String> 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),
|
|
@@ -400,9 +441,6 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
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: [
|
|
@@ -422,7 +460,7 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
eliminarProductoDelCarrito(index)),
|
|
|
IconButton(
|
|
|
icon: const Icon(Icons.remove),
|
|
|
- onPressed: () => quitarDelCarrito(item.producto)),
|
|
|
+ onPressed: () => quitarProductoDelCarrito(item)),
|
|
|
const SizedBox(width: 5),
|
|
|
Text('${item.cantidad}',
|
|
|
style: const TextStyle(
|
|
@@ -430,17 +468,104 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
const SizedBox(width: 5),
|
|
|
IconButton(
|
|
|
icon: const Icon(Icons.add),
|
|
|
- onPressed: () => agregarAlCarrito(item.producto)),
|
|
|
+ onPressed: () => incrementarProducto(item)),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
- 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.
|
|
|
+ if (item.selectableToppings.isNotEmpty)
|
|
|
+ ExpansionTile(
|
|
|
+ title: Text('Toppings',
|
|
|
+ style: const TextStyle(
|
|
|
+ fontWeight: FontWeight.bold, fontSize: 16)),
|
|
|
+ children: item.selectableToppings.entries.map((entry) {
|
|
|
+ final categoryId = entry.key;
|
|
|
+ final availableToppings = entry.value;
|
|
|
+ final categoria =
|
|
|
+ categorias.firstWhere((c) => c.id == categoryId);
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(left: 16.0),
|
|
|
+ child: Text(
|
|
|
+ categoria.descripcion!,
|
|
|
+ style: const TextStyle(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ fontSize: 16),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ...availableToppings.map((topping) {
|
|
|
+ ValueNotifier<bool> isSelectedNotifier =
|
|
|
+ ValueNotifier<bool>(
|
|
|
+ item.selectedToppings[categoryId]
|
|
|
+ ?.contains(topping.id) ??
|
|
|
+ false,
|
|
|
+ );
|
|
|
+
|
|
|
+ return ValueListenableBuilder<bool>(
|
|
|
+ valueListenable: isSelectedNotifier,
|
|
|
+ builder: (context, isSelected, _) {
|
|
|
+ return CheckboxListTile(
|
|
|
+ activeColor: AppTheme.primary,
|
|
|
+ title: Row(
|
|
|
+ mainAxisAlignment:
|
|
|
+ MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ Text(topping.nombre!),
|
|
|
+ if (double.tryParse(
|
|
|
+ topping.precio!) !=
|
|
|
+ 0.0)
|
|
|
+ Text(
|
|
|
+ '+\$${topping.precio}',
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.black,
|
|
|
+ fontSize: 13,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ value: isSelected,
|
|
|
+ onChanged: (bool? value) {
|
|
|
+ final maximoToppings =
|
|
|
+ categoria.maximo ?? 0;
|
|
|
+
|
|
|
+ if (value == true) {
|
|
|
+ if ((item.selectedToppings[categoryId]
|
|
|
+ ?.length ??
|
|
|
+ 0) >=
|
|
|
+ maximoToppings) {
|
|
|
+ item.selectedToppings[categoryId]!
|
|
|
+ .remove(item
|
|
|
+ .selectedToppings[
|
|
|
+ categoryId]!
|
|
|
+ .first);
|
|
|
+ }
|
|
|
+ item.selectedToppings[categoryId] ??=
|
|
|
+ <int>{};
|
|
|
+ item.selectedToppings[categoryId]!
|
|
|
+ .add(topping.id!);
|
|
|
+ } else {
|
|
|
+ item.selectedToppings[categoryId]
|
|
|
+ ?.remove(topping.id!);
|
|
|
+ if (item.selectedToppings[categoryId]
|
|
|
+ ?.isEmpty ??
|
|
|
+ false) {
|
|
|
+ item.selectedToppings
|
|
|
+ .remove(categoryId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ );
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ ),
|
|
|
+ Divider(),
|
|
|
],
|
|
|
);
|
|
|
},
|
|
@@ -454,11 +579,12 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
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'),
|
|
|
+ primary: AppTheme.tertiary,
|
|
|
+ textStyle: const TextStyle(fontSize: 22),
|
|
|
+ fixedSize: const Size(250, 50),
|
|
|
+ ),
|
|
|
+ child: Text('Finalizar Pedido',
|
|
|
+ style: TextStyle(color: AppTheme.quaternary)),
|
|
|
),
|
|
|
),
|
|
|
],
|
|
@@ -472,6 +598,22 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ void incrementarProducto(ItemCarrito item) {
|
|
|
+ setState(() {
|
|
|
+ item.cantidad++;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void quitarProductoDelCarrito(ItemCarrito item) {
|
|
|
+ setState(() {
|
|
|
+ if (item.cantidad > 1) {
|
|
|
+ item.cantidad--;
|
|
|
+ } else {
|
|
|
+ carrito.remove(item);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
Widget _buildProductsSection() {
|
|
|
return Column(
|
|
|
children: [
|
|
@@ -501,7 +643,15 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
child: Column(
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
children: [
|
|
|
- const Icon(Icons.fastfood, size: 80),
|
|
|
+ if (producto.imagen != null &&
|
|
|
+ File(producto.imagen!).existsSync())
|
|
|
+ Image.file(
|
|
|
+ File(producto.imagen!),
|
|
|
+ height: 120,
|
|
|
+ fit: BoxFit.cover,
|
|
|
+ )
|
|
|
+ else
|
|
|
+ const Icon(Icons.fastfood, size: 80),
|
|
|
const SizedBox(height: 8),
|
|
|
Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
@@ -536,13 +686,16 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
}
|
|
|
|
|
|
Widget _buildCategoryButtons() {
|
|
|
+ List<CategoriaProducto> categoriasFiltradas =
|
|
|
+ categorias.where((categoria) => categoria.esToping == 0).toList();
|
|
|
+
|
|
|
return Container(
|
|
|
- height: 50, // Define una altura fija para los botones
|
|
|
+ height: 50,
|
|
|
child: ListView.builder(
|
|
|
scrollDirection: Axis.horizontal,
|
|
|
- itemCount: categorias.length,
|
|
|
+ itemCount: categoriasFiltradas.length,
|
|
|
itemBuilder: (context, index) {
|
|
|
- final categoria = categorias[index];
|
|
|
+ final categoria = categoriasFiltradas[index];
|
|
|
bool isSelected = categoriaSeleccionada?.id == categoria.id;
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
@@ -554,8 +707,10 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
});
|
|
|
},
|
|
|
style: ElevatedButton.styleFrom(
|
|
|
- primary: isSelected ? AppTheme.primary : Colors.grey,
|
|
|
- onPrimary: Colors.white,
|
|
|
+ primary: isSelected ? AppTheme.tertiary : Colors.grey,
|
|
|
+ foregroundColor:
|
|
|
+ isSelected ? AppTheme.quaternary : AppTheme.secondary,
|
|
|
+ onPrimary: AppTheme.secondary,
|
|
|
),
|
|
|
child: Text(categoria.nombre!),
|
|
|
),
|
|
@@ -577,7 +732,7 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
borderRadius: BorderRadius.circular(12.0),
|
|
|
),
|
|
|
),
|
|
|
- onChanged: _onSearchChanged, // Usar el método de búsqueda aquí
|
|
|
+ onChanged: _onSearchChanged,
|
|
|
),
|
|
|
);
|
|
|
}
|