pedido_form.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. import 'package:flutter/foundation.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:yoshi_papas_app/widgets/widgets.dart';
  4. import '../../themes/themes.dart';
  5. class PedidoForm extends StatefulWidget {
  6. @override
  7. _PedidoFormState createState() => _PedidoFormState();
  8. }
  9. enum CustomizationStep { Base, Salsa, Aderezo, Toppings }
  10. class _PedidoFormState extends State<PedidoForm> {
  11. Map<String, bool> baseOptions = {};
  12. Map<String, bool> sauceOptions = {};
  13. Map<String, bool> dressingOptions = {};
  14. Map<String, bool> toppingOptions = {};
  15. bool isCustomizingProduct = false;
  16. Map<String, dynamic>? currentProductForCustomization;
  17. String? selectedBase;
  18. List<String> selectedSauce = [];
  19. List<String> selectedDressing = [];
  20. List<String> selectedToppings = [];
  21. List<Map<String, dynamic>> productsInCart = [];
  22. String currentCategory = 'Hamburguesa de pollo';
  23. Map<String, List<Map<String, dynamic>>> categoryProducts = {
  24. 'Hamburguesa de pollo': [
  25. {
  26. 'name': 'Rebanada de queso',
  27. 'price': 25,
  28. 'category': 'Hamburguesa de pollo'
  29. },
  30. {
  31. 'name': 'Porción de queso',
  32. 'price': 15,
  33. 'category': 'Hamburguesa de pollo'
  34. },
  35. {
  36. 'name': 'Hamburguesa de pollo',
  37. 'price': 110,
  38. 'category': 'Hamburguesa de pollo'
  39. },
  40. ],
  41. 'Postres': [
  42. {'name': 'Muffin', 'price': 35, 'category': 'Postres'},
  43. {'name': 'Rebanada de Pay de Nuez', 'price': 65, 'category': 'Postres'},
  44. ],
  45. 'Cono de papas': [
  46. {
  47. 'name': 'Cono de papas grande',
  48. 'price': 120,
  49. 'category': 'Cono de papas'
  50. },
  51. {
  52. 'name': 'Cono de papas mediano',
  53. 'price': 85,
  54. 'category': 'Cono de papas'
  55. },
  56. ],
  57. };
  58. List<Map<String, dynamic>> products = [];
  59. CustomizationStep _currentStep = CustomizationStep.Base;
  60. late Map<CustomizationStep, Widget> _stepWidgets;
  61. @override
  62. void initState() {
  63. super.initState();
  64. // Inicializa con la categoría actual
  65. products = categoryProducts[currentCategory]!;
  66. initializeCheckboxStates();
  67. _stepWidgets = {
  68. CustomizationStep.Base: _buildBaseOptions(),
  69. CustomizationStep.Salsa: _buildSauceOptions(),
  70. CustomizationStep.Aderezo: _buildDressingOptions(),
  71. CustomizationStep.Toppings: _buildToppingsOptions(),
  72. };
  73. }
  74. void customizeProduct(Map<String, dynamic> product) {
  75. if (product['category'] == 'Cono de papas') {
  76. setState(() {
  77. isCustomizingProduct = true;
  78. currentProductForCustomization = product;
  79. });
  80. } else {
  81. addToCart(product);
  82. }
  83. }
  84. void initializeCheckboxStates() {
  85. // Inicializa los mapas de opciones con valores false
  86. for (var base in [
  87. 'Papa Gajo',
  88. 'Papa Regilla',
  89. 'Papa Curly',
  90. 'Papa Smile',
  91. 'Papa Francesa'
  92. ]) {
  93. baseOptions[base] = false;
  94. }
  95. for (var sauce in [
  96. 'BBQ',
  97. 'HOTBBQ',
  98. 'BUFFALO',
  99. 'TERIYAKI',
  100. 'PARMESAN GARLIC',
  101. 'MANGO HABANERO'
  102. ]) {
  103. sauceOptions[sauce] = false;
  104. }
  105. for (var dressing in ['QUESO AMARILLO', 'RANCH', 'CHIPOTLE', 'KETCHUP']) {
  106. dressingOptions[dressing] = false;
  107. }
  108. for (var topping in [
  109. 'JALAPEÑO',
  110. 'QUESO BLANCO',
  111. 'TAKIS',
  112. 'RUFFLES',
  113. 'QUESO PARMESANO',
  114. 'ELOTE'
  115. ]) {
  116. toppingOptions[topping] = false;
  117. }
  118. }
  119. void addToCart(Map<String, dynamic> product) {
  120. // Si es un "Cono de papas" y estamos personalizando
  121. if (product['category'] == 'Cono de papas' && isCustomizingProduct) {
  122. final Map<String, dynamic> customizedProduct = {
  123. ...product,
  124. 'customizations': {
  125. 'base': selectedBase,
  126. 'sauce': selectedSauce,
  127. 'dressing': selectedDressing,
  128. 'toppings': selectedToppings,
  129. },
  130. 'quantity':
  131. 1, // Asegúrate de que cada producto personalizado tenga una cantidad inicial de 1
  132. };
  133. setState(() {
  134. productsInCart
  135. .add(customizedProduct); // Añade el producto personalizado
  136. isCustomizingProduct = false; // Termina la personalización
  137. resetCustomizations(); // Llama a un método que restablecerá las personalizaciones
  138. });
  139. } else {
  140. // Si no es un "Cono de papas" o no estamos personalizando, añade directamente al carrito
  141. setState(() {
  142. int index = productsInCart.indexWhere((p) =>
  143. p['name'] == product['name'] &&
  144. p['customizations'] ==
  145. product[
  146. 'customizations']); // Comparar también las personalizaciones
  147. if (index != -1) {
  148. productsInCart[index]['quantity'] += 1;
  149. } else {
  150. productsInCart.add(
  151. {...product, 'quantity': 1}); // Añade con cantidad inicial de 1
  152. }
  153. });
  154. }
  155. }
  156. void resetCustomizations() {
  157. // Restablece las variables de estado de las personalizaciones
  158. selectedBase = null;
  159. selectedSauce = [];
  160. selectedDressing = [];
  161. selectedToppings = [];
  162. // Restablecer cualquier otro estado de personalización aquí
  163. }
  164. void finalizeCustomization() {
  165. // Aquí debes construir el producto basado en las selecciones de personalización
  166. selectedBase = baseOptions.entries
  167. .firstWhere((entry) => entry.value, orElse: () => MapEntry('', false))
  168. .key;
  169. selectedSauce = sauceOptions.entries
  170. .where((entry) => entry.value)
  171. .map((e) => e.key)
  172. .toList();
  173. selectedDressing = dressingOptions.entries
  174. .where((entry) => entry.value)
  175. .map((e) => e.key)
  176. .toList();
  177. selectedToppings = toppingOptions.entries
  178. .where((entry) => entry.value)
  179. .map((e) => e.key)
  180. .toList();
  181. Map<String, dynamic> customizedProduct = {
  182. ...currentProductForCustomization!,
  183. 'customizations': {
  184. 'base': selectedBase,
  185. 'sauce': selectedSauce,
  186. 'dressing': selectedDressing,
  187. 'toppings': selectedToppings,
  188. }
  189. };
  190. addToCart(customizedProduct);
  191. setState(() {
  192. isCustomizingProduct = false;
  193. currentProductForCustomization = null;
  194. initializeCheckboxStates(); // Reinicia los estados de los checkbox
  195. });
  196. }
  197. Widget buildCategoryButtons() {
  198. return Row(
  199. mainAxisAlignment: MainAxisAlignment.center,
  200. children: categoryProducts.keys.map((String key) {
  201. return Padding(
  202. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  203. child: OutlinedButton(
  204. onPressed: () {
  205. setState(() {
  206. currentCategory = key;
  207. products = categoryProducts[key]!;
  208. });
  209. },
  210. style: OutlinedButton.styleFrom(
  211. backgroundColor: Colors.white,
  212. foregroundColor: Colors.black,
  213. side: BorderSide(
  214. width: 2.0,
  215. color: currentCategory == key ? AppTheme.primary : Colors.grey,
  216. ),
  217. ),
  218. child: Text(key),
  219. ),
  220. );
  221. }).toList(),
  222. );
  223. }
  224. @override
  225. Widget build(BuildContext context) {
  226. return Scaffold(
  227. appBar: encabezado(
  228. titulo: "CREAR PEDIDO",
  229. ),
  230. body: Row(
  231. children: [
  232. // Sección izquierda con la lista del carrito - 30%
  233. Flexible(
  234. flex: 3,
  235. child: tarjeta(
  236. Column(
  237. children: [
  238. // Encabezados de la lista
  239. const Padding(
  240. padding:
  241. EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
  242. child: Row(
  243. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  244. children: [
  245. Text('Producto',
  246. style: TextStyle(
  247. fontWeight: FontWeight.bold, fontSize: 18)),
  248. Text('Cantidad',
  249. style: TextStyle(
  250. fontWeight: FontWeight.bold, fontSize: 18)),
  251. ],
  252. ),
  253. ),
  254. // Lista de productos
  255. Expanded(
  256. child: ListView.builder(
  257. itemCount: productsInCart.length,
  258. itemBuilder: (context, index) {
  259. var product = productsInCart[index];
  260. return ListTile(
  261. title: _buildProductItem(product),
  262. trailing: Row(
  263. mainAxisSize: MainAxisSize.min,
  264. children: [
  265. IconButton(
  266. icon: const Icon(Icons.remove),
  267. onPressed: () {
  268. // Lógica para disminuir la cantidad
  269. setState(() {
  270. if (product['quantity'] > 1) {
  271. productsInCart[index]['quantity'] -= 1;
  272. } else {
  273. productsInCart.removeAt(index);
  274. }
  275. });
  276. },
  277. ),
  278. Text('${product['quantity'] ?? 1}'),
  279. IconButton(
  280. icon: const Icon(Icons.add),
  281. onPressed: () {
  282. // Lógica para aumentar la cantidad
  283. setState(() {
  284. productsInCart[index]['quantity'] += 1;
  285. });
  286. },
  287. ),
  288. ],
  289. ),
  290. );
  291. },
  292. ),
  293. ),
  294. ElevatedButton(
  295. style: ButtonStyle(
  296. backgroundColor:
  297. MaterialStatePropertyAll(AppTheme.primary),
  298. textStyle: const MaterialStatePropertyAll(
  299. TextStyle(fontSize: 22)),
  300. foregroundColor:
  301. const MaterialStatePropertyAll(Colors.black),
  302. padding: const MaterialStatePropertyAll(
  303. EdgeInsets.fromLTRB(80, 20, 80, 20))),
  304. onPressed: () {
  305. // Aquí agregarías la lógica para guardar el pedido
  306. },
  307. child: const Text('Guardar'),
  308. ),
  309. ],
  310. ),
  311. color: Colors.white,
  312. padding: 8.0,
  313. ),
  314. ),
  315. const SizedBox(
  316. width: 35,
  317. ),
  318. // Sección derecha con los productos disponibles - 70%
  319. Flexible(
  320. flex: 7,
  321. child: Column(
  322. children: [
  323. // Botones de categorías de productos
  324. buildCategoryButtons(),
  325. // GridView de productos
  326. Expanded(
  327. child: isCustomizingProduct
  328. ? buildCustomizationOptions()
  329. : buildProductGrid(),
  330. ),
  331. ],
  332. ),
  333. ),
  334. ],
  335. ),
  336. );
  337. }
  338. Widget _buildProductItem(Map<String, dynamic> product) {
  339. List<Widget> customizationWidgets = [];
  340. if (product.containsKey('customizations')) {
  341. Map customizations = product['customizations'];
  342. customizationWidgets.addAll([
  343. Text(' - Base: ${customizations['base']}'),
  344. ...customizations['sauce']
  345. .map<Widget>((sauce) => Text(' - Salsa: $sauce'))
  346. .toList(),
  347. ...customizations['dressing']
  348. .map<Widget>((dressing) => Text(' - Aderezo: $dressing'))
  349. .toList(),
  350. ...customizations['toppings']
  351. .map<Widget>((topping) => Text(' - Topping: $topping'))
  352. .toList(),
  353. ]);
  354. }
  355. return Column(
  356. crossAxisAlignment: CrossAxisAlignment.start,
  357. children: [
  358. Text(product['name']),
  359. ...customizationWidgets,
  360. ],
  361. );
  362. }
  363. Widget buildProductGrid() {
  364. return tarjeta(
  365. GridView.builder(
  366. itemCount: products.length,
  367. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  368. crossAxisCount: 2, // Número de columnas
  369. childAspectRatio: 3 / 2, // Proporción de cada tarjeta
  370. crossAxisSpacing: 10, // Espaciado horizontal
  371. mainAxisSpacing: 10, // Espaciado vertical
  372. ),
  373. itemBuilder: (context, index) {
  374. final product = products[index];
  375. return tarjeta(
  376. InkWell(
  377. onTap: () => customizeProduct(product),
  378. child: Column(
  379. mainAxisAlignment: MainAxisAlignment.center,
  380. children: [
  381. // Añade el ícono aquí
  382. const Icon(Icons.fastfood,
  383. size: 80), // Tamaño del ícono ajustable
  384. const SizedBox(height: 8), // Espacio entre ícono y texto
  385. Text(
  386. product['name'],
  387. style: const TextStyle(fontSize: 16),
  388. textAlign: TextAlign.center,
  389. ),
  390. const SizedBox(height: 8), // Espacio entre texto y precio
  391. Text(
  392. '\$${product['price']}',
  393. style:
  394. const TextStyle(fontSize: 24, color: Color(0xFF008000)),
  395. textAlign: TextAlign.center,
  396. ),
  397. ],
  398. ),
  399. ),
  400. color: const Color(0xFFF4F4F4),
  401. padding: 8.0,
  402. );
  403. },
  404. ),
  405. color: Colors.white,
  406. padding: 8.0,
  407. );
  408. }
  409. Widget buildCustomizationOptions() {
  410. Widget currentStepWidget;
  411. switch (_currentStep) {
  412. case CustomizationStep.Base:
  413. currentStepWidget = _buildBaseOptions();
  414. break;
  415. case CustomizationStep.Salsa:
  416. currentStepWidget = _buildSauceOptions();
  417. break;
  418. case CustomizationStep.Aderezo:
  419. currentStepWidget = _buildDressingOptions();
  420. break;
  421. case CustomizationStep.Toppings:
  422. currentStepWidget = _buildToppingsOptions();
  423. break;
  424. default:
  425. currentStepWidget = SizedBox.shrink();
  426. }
  427. // Solo muestra el botón de confirmación si isCustomizingProduct es true.
  428. Widget confirmButton = isCustomizingProduct
  429. ? Padding(
  430. padding: const EdgeInsets.symmetric(vertical: 16.0),
  431. child: _buildConfirmButton(),
  432. )
  433. : SizedBox.shrink();
  434. return tarjeta(
  435. Column(
  436. children: [
  437. Expanded(
  438. child: Row(
  439. children: [
  440. Flexible(
  441. flex: 3,
  442. child: ListView(
  443. children: CustomizationStep.values.map((step) {
  444. // Verifica si el paso es Salsa, Aderezo o Toppings para añadir el texto " (Max 2)"
  445. String stepName = describeEnum(step);
  446. if (step == CustomizationStep.Salsa ||
  447. step == CustomizationStep.Aderezo ||
  448. step == CustomizationStep.Toppings) {
  449. stepName +=
  450. " (Max 2)"; // Agrega " (Max 2)" al nombre del paso+
  451. }
  452. return ListTile(
  453. title: Text(stepName),
  454. selected: _currentStep == step,
  455. onTap: () => setState(() => _currentStep = step),
  456. );
  457. }).toList(),
  458. ),
  459. ),
  460. Flexible(
  461. flex: 7,
  462. child: currentStepWidget,
  463. ),
  464. ],
  465. ),
  466. ),
  467. Padding(
  468. padding: const EdgeInsets.symmetric(vertical: 16.0),
  469. child: _buildConfirmButton(),
  470. ), // Incluir el botón de confirmación aquí
  471. ],
  472. ),
  473. color: Colors.white,
  474. padding: 8.0,
  475. );
  476. }
  477. Widget buildCheckboxListTile({
  478. required String title,
  479. required Map<String, bool> optionsMap,
  480. required Function(String, bool?) onChanged,
  481. }) {
  482. return CheckboxListTile(
  483. title: Text(title),
  484. value: optionsMap[title] ?? false,
  485. onChanged: (bool? value) {
  486. setState(() {
  487. onChanged(title, value);
  488. });
  489. },
  490. secondary: Image.asset('assets/JoshiLogo.png', width: 30),
  491. );
  492. }
  493. Widget _buildBaseOptions() {
  494. return GridView.count(
  495. crossAxisCount: 3,
  496. children: baseOptions.keys.map((String key) {
  497. bool isSelected =
  498. baseOptions[key] ?? false; // Determina si está seleccionado
  499. return GestureDetector(
  500. onTap: () {
  501. setState(() {
  502. baseOptions.keys.forEach(
  503. (k) => baseOptions[k] = false); // Desmarca todos primero
  504. baseOptions[key] = true; // Marca el seleccionado
  505. });
  506. },
  507. child: Container(
  508. decoration: BoxDecoration(
  509. color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
  510. border: Border.all(color: isSelected ? Colors.red : Colors.grey),
  511. ),
  512. child: Center(
  513. child: Text(key,
  514. style: TextStyle(
  515. color: isSelected ? Colors.white : Colors.black)),
  516. ),
  517. ),
  518. );
  519. }).toList(),
  520. );
  521. }
  522. Widget _buildSauceOptions() {
  523. return GridView.count(
  524. crossAxisCount: 3,
  525. children: sauceOptions.keys.map((String key) {
  526. bool isSelected = sauceOptions[key] ?? false;
  527. return GestureDetector(
  528. onTap: () {
  529. int selectedCount = sauceOptions.values.where((b) => b).length;
  530. setState(() {
  531. // Si la salsa ya está seleccionada, la deselecciona.
  532. if (isSelected) {
  533. sauceOptions[key] = false;
  534. } else {
  535. // Si se están seleccionando menos de 2 salsas, permite esta selección.
  536. if (selectedCount < 2) {
  537. sauceOptions[key] = true;
  538. } else {
  539. // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
  540. final firstSelected = sauceOptions.keys.firstWhere(
  541. (k) => sauceOptions[k]!,
  542. orElse: () => '',
  543. );
  544. if (firstSelected.isNotEmpty) {
  545. sauceOptions[firstSelected] = false;
  546. sauceOptions[key] = true;
  547. }
  548. }
  549. }
  550. });
  551. },
  552. child: Container(
  553. decoration: BoxDecoration(
  554. color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
  555. border: Border.all(color: Colors.grey),
  556. ),
  557. child: Center(
  558. child: Text(key,
  559. style: TextStyle(
  560. color: isSelected ? Colors.white : Colors.black)),
  561. ),
  562. ),
  563. );
  564. }).toList(),
  565. );
  566. }
  567. Widget _buildDressingOptions() {
  568. return GridView.count(
  569. crossAxisCount: 3,
  570. children: dressingOptions.keys.map((String key) {
  571. bool isSelected = dressingOptions[key] ?? false;
  572. return GestureDetector(
  573. onTap: () {
  574. int selectedCount = dressingOptions.values.where((b) => b).length;
  575. setState(() {
  576. // Si la salsa ya está seleccionada, la deselecciona.
  577. if (isSelected) {
  578. dressingOptions[key] = false;
  579. } else {
  580. // Si se están seleccionando menos de 2 salsas, permite esta selección.
  581. if (selectedCount < 2) {
  582. dressingOptions[key] = true;
  583. } else {
  584. // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
  585. final firstSelected = dressingOptions.keys.firstWhere(
  586. (k) => dressingOptions[k]!,
  587. orElse: () => '',
  588. );
  589. if (firstSelected.isNotEmpty) {
  590. dressingOptions[firstSelected] = false;
  591. dressingOptions[key] = true;
  592. }
  593. }
  594. }
  595. });
  596. },
  597. child: Container(
  598. decoration: BoxDecoration(
  599. color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
  600. border: Border.all(color: Colors.grey),
  601. ),
  602. child: Center(
  603. child: Text(key,
  604. style: TextStyle(
  605. color: isSelected ? Colors.white : Colors.black)),
  606. ),
  607. ),
  608. );
  609. }).toList(),
  610. );
  611. }
  612. Widget _buildToppingsOptions() {
  613. return GridView.count(
  614. crossAxisCount: 3,
  615. children: toppingOptions.keys.map((String key) {
  616. bool isSelected = toppingOptions[key] ?? false;
  617. return GestureDetector(
  618. onTap: () {
  619. int selectedCount = toppingOptions.values.where((b) => b).length;
  620. setState(() {
  621. // Si la salsa ya está seleccionada, la deselecciona.
  622. if (isSelected) {
  623. toppingOptions[key] = false;
  624. } else {
  625. // Si se están seleccionando menos de 2 salsas, permite esta selección.
  626. if (selectedCount < 2) {
  627. toppingOptions[key] = true;
  628. } else {
  629. // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
  630. final firstSelected = toppingOptions.keys.firstWhere(
  631. (k) => toppingOptions[k]!,
  632. orElse: () => '',
  633. );
  634. if (firstSelected.isNotEmpty) {
  635. toppingOptions[firstSelected] = false;
  636. toppingOptions[key] = true;
  637. }
  638. }
  639. }
  640. });
  641. },
  642. child: Container(
  643. decoration: BoxDecoration(
  644. color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
  645. border: Border.all(color: Colors.grey),
  646. ),
  647. child: Center(
  648. child: Text(key,
  649. style: TextStyle(
  650. color: isSelected ? Colors.white : Colors.black)),
  651. ),
  652. ),
  653. );
  654. }).toList(),
  655. );
  656. }
  657. Widget _buildConfirmButton() {
  658. return ElevatedButton(
  659. style: ElevatedButton.styleFrom(
  660. primary: AppTheme.primary, // Color del botón
  661. onPrimary: Colors.black,
  662. textStyle: const TextStyle(fontSize: 22),
  663. padding: const EdgeInsets.fromLTRB(80, 20, 80, 20)),
  664. onPressed: () {
  665. finalizeCustomization(); // Este método creará el pedido personalizado
  666. },
  667. child: Text('Confirmar Pedido'),
  668. );
  669. }
  670. }