|
@@ -1,7 +1,10 @@
|
|
|
+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
|
|
@@ -11,8 +14,10 @@ class PedidoForm extends StatefulWidget {
|
|
|
class ItemCarrito {
|
|
|
Producto producto;
|
|
|
int cantidad;
|
|
|
+ Map<String, dynamic>? customizaciones;
|
|
|
|
|
|
- ItemCarrito({required this.producto, this.cantidad = 1});
|
|
|
+ ItemCarrito(
|
|
|
+ {required this.producto, this.cantidad = 1, this.customizaciones});
|
|
|
}
|
|
|
|
|
|
class _PedidoFormState extends State<PedidoForm> {
|
|
@@ -24,6 +29,25 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
List<CategoriaProducto> categorias = [];
|
|
|
List<Producto> productos = [];
|
|
|
List<ItemCarrito> carrito = [];
|
|
|
+ TopingViewModel _topingViewModel = TopingViewModel();
|
|
|
+ TopingCategoriaViewModel _categoriaTopingViewModel =
|
|
|
+ TopingCategoriaViewModel();
|
|
|
+ List<Toping> _topingsDisponibles = []; // Lista de topings disponibles
|
|
|
+ Producto? _productoActual; // Producto actual seleccionado para customización
|
|
|
+ bool isCustomizingProduct = false;
|
|
|
+ Map<String, dynamic>? currentProductForCustomization;
|
|
|
+ String? selectedBase;
|
|
|
+ List<String> selectedSauce = [];
|
|
|
+ List<String> selectedDressing = [];
|
|
|
+ List<String> selectedToppings = [];
|
|
|
+ Map<String, bool> baseOptions = {};
|
|
|
+ Map<String, bool> sauceOptions = {};
|
|
|
+ Map<String, bool> dressingOptions = {};
|
|
|
+ Map<String, bool> toppingOptions = {};
|
|
|
+ List<TopingCategoria> categoriasDeTopings = []; // Añade esta línea
|
|
|
+ TopingCategoria? _currentTopingCategoriaSeleccionada; // Añade esta línea
|
|
|
+ List<Toping> _topingsFiltrados = []; // Añade esta línea
|
|
|
+ Map<int, List<String>> selectedToppingsByCategory = {};
|
|
|
|
|
|
double calcularTotalPedido() {
|
|
|
double total = 0;
|
|
@@ -37,6 +61,80 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
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
|
|
@@ -98,6 +196,53 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ void addToCart(Producto producto, {Map<String, dynamic>? 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<String, dynamic> 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(
|
|
@@ -113,7 +258,9 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
SizedBox(width: 35),
|
|
|
Flexible(
|
|
|
flex: 7,
|
|
|
- child: _buildProductsSection(),
|
|
|
+ child: isCustomizingProduct
|
|
|
+ ? buildCustomizationOptions()
|
|
|
+ : _buildProductsSection(),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
@@ -242,10 +389,14 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
return Card(
|
|
|
child: InkWell(
|
|
|
onTap: () {
|
|
|
- setState(() {
|
|
|
- // Aquí manejas la adición de productos al carrito
|
|
|
+ // 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,
|
|
@@ -323,4 +474,287 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
],
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ 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'),
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|