pedido_form.dart 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:intl/intl.dart';
  6. import 'package:provider/provider.dart';
  7. import 'package:yoshi_papas_app/views/pedido/pedido_ticket.dart';
  8. import '../../themes/themes.dart';
  9. import '../../models/models.dart';
  10. import '../../viewmodels/viewmodels.dart';
  11. import 'package:collection/collection.dart';
  12. import '../../widgets/widgets.dart';
  13. class PedidoForm extends StatefulWidget {
  14. @override
  15. _PedidoFormState createState() => _PedidoFormState();
  16. }
  17. class _PedidoFormState extends State<PedidoForm> {
  18. final _busqueda = TextEditingController(text: '');
  19. CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
  20. ProductoViewModel pvm = ProductoViewModel();
  21. PedidoViewModel pedvm = PedidoViewModel();
  22. bool _isLoading = false;
  23. CategoriaProducto? categoriaSeleccionada;
  24. List<CategoriaProducto> categorias = [];
  25. List<Producto> productos = [];
  26. List<ItemCarrito> carrito = [];
  27. Producto? _productoActual;
  28. bool _estadoBusqueda = false;
  29. Pedido? pedidoActual;
  30. ScrollController _gridViewController = ScrollController();
  31. final _searchController = TextEditingController();
  32. final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
  33. double calcularTotalPedido() {
  34. double total = 0;
  35. for (var item in carrito) {
  36. total += double.parse(item.producto.precio!) * item.cantidad;
  37. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  38. for (int toppingId in selectedToppingIds) {
  39. Producto? topping = item.selectableToppings[categoryId]?.firstWhere(
  40. (topping) => topping.id == toppingId,
  41. orElse: () => Producto(precio: '0'));
  42. if (topping != null) {
  43. total += (double.tryParse(topping.precio!) ?? 0) * item.cantidad;
  44. }
  45. }
  46. });
  47. }
  48. return total;
  49. }
  50. @override
  51. void initState() {
  52. super.initState();
  53. cargarCategoriasIniciales().then((_) {
  54. if (categorias.isNotEmpty) {
  55. categoriaSeleccionada = categorias.first;
  56. cargarProductosPorCategoria(categoriaSeleccionada!.id);
  57. }
  58. });
  59. }
  60. _onSearchChanged(String value) {
  61. if (value.isEmpty) {
  62. cargarProductosIniciales();
  63. } else {
  64. Provider.of<ProductoViewModel>(context, listen: false)
  65. .fetchLocalByName(nombre: value);
  66. }
  67. }
  68. void _finalizeOrder() async {
  69. if (carrito.isEmpty) {
  70. showDialog(
  71. context: context,
  72. builder: (BuildContext context) {
  73. return AlertDialog(
  74. title: const Text('Pedido vacío',
  75. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
  76. content: const Text(
  77. 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.',
  78. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)),
  79. shape:
  80. RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  81. actions: <Widget>[
  82. TextButton(
  83. style: TextButton.styleFrom(
  84. padding: EdgeInsets.fromLTRB(30, 20, 30, 20),
  85. foregroundColor: AppTheme.quaternary,
  86. backgroundColor: AppTheme.tertiary),
  87. onPressed: () {
  88. Navigator.of(context).pop();
  89. },
  90. child: const Text('Aceptar', style: TextStyle(fontSize: 18)),
  91. ),
  92. ],
  93. );
  94. },
  95. );
  96. return;
  97. } else {
  98. int? pedidoId = pedidoActual?.id;
  99. if (pedidoId != null) {
  100. Navigator.of(context).pop();
  101. }
  102. }
  103. await _promptForCustomerName();
  104. }
  105. Future<void> _promptForCustomerName() async {
  106. TextEditingController nombreController = TextEditingController();
  107. TextEditingController comentarioController = TextEditingController();
  108. String errorMessage = '';
  109. bool? shouldSave = await showDialog<bool>(
  110. context: context,
  111. builder: (BuildContext context) {
  112. return StatefulBuilder(
  113. builder: (context, setState) {
  114. return AlertDialog(
  115. actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
  116. title: const Text(
  117. 'Finalizar Pedido',
  118. style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
  119. ),
  120. content: Column(
  121. mainAxisSize: MainAxisSize.min,
  122. children: [
  123. AppTextField(
  124. controller: nombreController,
  125. etiqueta: 'Nombre',
  126. hintText: "Nombre del Cliente",
  127. errorText: errorMessage.isEmpty ? null : errorMessage,
  128. onChanged: (value) {
  129. if (value.trim().isEmpty) {
  130. setState(() => errorMessage =
  131. "El nombre del cliente es obligatorio.");
  132. } else {
  133. setState(() => errorMessage = '');
  134. }
  135. },
  136. ),
  137. const SizedBox(height: 10),
  138. AppTextField(
  139. controller: comentarioController,
  140. etiqueta: 'Comentarios (opcional)',
  141. hintText: 'Comentarios',
  142. maxLines: 3,
  143. ),
  144. ],
  145. ),
  146. actions: <Widget>[
  147. Row(
  148. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  149. children: [
  150. TextButton(
  151. child: const Text('Cancelar',
  152. style: TextStyle(fontSize: 18)),
  153. onPressed: () {
  154. Navigator.of(context).pop(false);
  155. },
  156. style: ButtonStyle(
  157. padding: MaterialStatePropertyAll(
  158. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  159. backgroundColor:
  160. MaterialStatePropertyAll(AppTheme.primary),
  161. foregroundColor:
  162. MaterialStatePropertyAll(AppTheme.secondary)),
  163. ),
  164. const SizedBox(width: 100),
  165. TextButton(
  166. child:
  167. const Text('Guardar', style: TextStyle(fontSize: 18)),
  168. onPressed: () {
  169. if (nombreController.text.trim().isEmpty) {
  170. setState(() => errorMessage =
  171. "El nombre del cliente es obligatorio.");
  172. return;
  173. }
  174. Navigator.of(context).pop(true);
  175. },
  176. style: ButtonStyle(
  177. padding: MaterialStatePropertyAll(
  178. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  179. backgroundColor:
  180. MaterialStatePropertyAll(AppTheme.tertiary),
  181. foregroundColor:
  182. MaterialStatePropertyAll(AppTheme.quaternary)),
  183. ),
  184. ],
  185. )
  186. ],
  187. );
  188. },
  189. );
  190. },
  191. );
  192. if (shouldSave ?? false) {
  193. prepararPedidoActual(nombreController.text, comentarioController.text);
  194. }
  195. }
  196. void prepararPedidoActual(String nombreCliente, String comentarios) async {
  197. DateTime now = DateTime.now();
  198. String formattedDate = DateFormat('dd-MM-yyyy kk:mm:ss').format(now);
  199. double totalPedido = calcularTotalPedido();
  200. Pedido nuevoPedido = Pedido(
  201. peticion: formattedDate,
  202. nombreCliente: nombreCliente,
  203. comentarios: comentarios,
  204. estatus: "NUEVO",
  205. totalPedido: totalPedido,
  206. );
  207. List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
  208. List<PedidoProductoTopping> selectedToppings = [];
  209. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  210. for (int toppingId in selectedToppingIds) {
  211. selectedToppings.add(PedidoProductoTopping(
  212. idTopping: toppingId,
  213. ));
  214. }
  215. });
  216. return PedidoProducto(
  217. idProducto: item.producto.id,
  218. producto: item.producto,
  219. costoUnitario: item.producto.precio,
  220. cantidad: item.cantidad,
  221. comentario: comentarios,
  222. toppings: selectedToppings,
  223. );
  224. }).toList();
  225. nuevoPedido.productos = listaPedidoProducto;
  226. bool result = await Provider.of<PedidoViewModel>(context, listen: false)
  227. .guardarPedidoLocal(pedido: nuevoPedido);
  228. if (result) {
  229. imprimirTicketsJuntos(nuevoPedido);
  230. Navigator.of(context).pop();
  231. } else {
  232. print("Error al guardar el pedido");
  233. }
  234. }
  235. void _limpiarBusqueda() async {
  236. setState(() {
  237. _busqueda.text = '';
  238. _estadoBusqueda = false;
  239. });
  240. await cargarCategoriasIniciales();
  241. }
  242. Future<void> cargarProductosIniciales() async {
  243. setState(() => _isLoading = true);
  244. await Provider.of<ProductoViewModel>(context, listen: false)
  245. .fetchLocalAll();
  246. productos =
  247. Provider.of<ProductoViewModel>(context, listen: false).productos;
  248. setState(() => _isLoading = false);
  249. }
  250. @override
  251. void dispose() {
  252. _gridViewController.dispose();
  253. _searchController.dispose();
  254. super.dispose();
  255. }
  256. Future<void> cargarCategoriasIniciales() async {
  257. setState(() => _isLoading = true);
  258. await Provider.of<CategoriaProductoViewModel>(context, listen: false)
  259. .fetchLocalAll();
  260. categorias = Provider.of<CategoriaProductoViewModel>(context, listen: false)
  261. .categoriaProductos;
  262. if (categorias.isNotEmpty) {
  263. categoriaSeleccionada = categorias.first;
  264. cargarProductosPorCategoria(categoriaSeleccionada!.id);
  265. }
  266. setState(() => _isLoading = false);
  267. if (categorias.isNotEmpty) {
  268. categoriaSeleccionada = categorias.first;
  269. }
  270. }
  271. void cargarProductosPorCategoria(int categoriaId) async {
  272. setState(() => _isLoading = true);
  273. await Provider.of<ProductoViewModel>(context, listen: false)
  274. .fetchAllByCategory(categoriaId);
  275. productos =
  276. Provider.of<ProductoViewModel>(context, listen: false).productos;
  277. setState(() => _isLoading = false);
  278. }
  279. void agregarAlCarrito(Producto producto) async {
  280. var existente = carrito.firstWhereOrNull((item) =>
  281. item.producto.id == producto.id &&
  282. mapEquals(item.selectedToppings, {}));
  283. if (existente != null) {
  284. setState(() {
  285. existente.cantidad++;
  286. });
  287. } else {
  288. Map<int, List<Producto>> toppingsSeleccionables =
  289. await obtenerToppingsSeleccionables(producto);
  290. setState(() {
  291. carrito.add(ItemCarrito(
  292. producto: producto,
  293. cantidad: 1,
  294. selectableToppings: toppingsSeleccionables,
  295. ));
  296. });
  297. }
  298. }
  299. Future<Map<int, List<Producto>>> obtenerToppingsSeleccionables(
  300. Producto producto) async {
  301. Map<int, List<Producto>> toppingsSeleccionables = {};
  302. final toppingCategories =
  303. await pvm.obtenerToppingsPorProducto(producto.id!);
  304. for (int toppingId in toppingCategories) {
  305. Producto? topping = await pvm.obtenerProductoPorId(toppingId);
  306. if (topping != null && topping.idCategoria != null) {
  307. toppingsSeleccionables[topping.idCategoria!] ??= [];
  308. toppingsSeleccionables[topping.idCategoria]!.add(topping);
  309. }
  310. }
  311. return toppingsSeleccionables;
  312. }
  313. void quitarDelCarrito(Producto producto) {
  314. setState(() {
  315. var indice =
  316. carrito.indexWhere((item) => item.producto.id == producto.id);
  317. if (indice != -1) {
  318. if (carrito[indice].cantidad > 1) {
  319. carrito[indice].cantidad--;
  320. } else {
  321. carrito.removeAt(indice);
  322. }
  323. }
  324. });
  325. }
  326. void addToCart(Producto producto) {
  327. var existingIndex = carrito.indexWhere((item) =>
  328. item.producto.id == producto.id &&
  329. mapEquals(item.selectedToppings, {}));
  330. if (existingIndex != -1) {
  331. setState(() {
  332. carrito[existingIndex].cantidad++;
  333. });
  334. } else {
  335. setState(() {
  336. carrito.add(ItemCarrito(producto: producto, cantidad: 1));
  337. });
  338. }
  339. }
  340. void finalizeCustomization() {
  341. if (_productoActual != null) {
  342. addToCart(_productoActual!);
  343. setState(() {
  344. _productoActual = null;
  345. });
  346. }
  347. }
  348. @override
  349. Widget build(BuildContext context) {
  350. return Scaffold(
  351. appBar: AppBar(
  352. title:
  353. Text("Crear Pedido", style: TextStyle(color: AppTheme.secondary)),
  354. iconTheme: IconThemeData(color: AppTheme.secondary)),
  355. body: Row(
  356. children: [
  357. Flexible(
  358. flex: 3,
  359. child: _buildCartSection(),
  360. ),
  361. SizedBox(width: 35),
  362. Flexible(flex: 7, child: _buildProductsSection()),
  363. ],
  364. ),
  365. );
  366. }
  367. Widget _buildTotalSection() {
  368. double total = calcularTotalPedido();
  369. String formattedTotal = _numberFormat.format(total);
  370. return Padding(
  371. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  372. child: Row(
  373. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  374. children: [
  375. const Text('Total',
  376. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  377. Text("\$$formattedTotal",
  378. style:
  379. const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  380. ],
  381. ),
  382. );
  383. }
  384. Widget _buildCartSection() {
  385. return Card(
  386. margin: const EdgeInsets.all(8.0),
  387. child: Column(
  388. children: [
  389. const Padding(
  390. padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
  391. child: Row(
  392. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  393. children: [
  394. Text('Producto',
  395. style:
  396. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  397. Text('Cantidad',
  398. style:
  399. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  400. ],
  401. ),
  402. ),
  403. Expanded(
  404. child: ListView.builder(
  405. itemCount: carrito.length,
  406. itemBuilder: (context, index) {
  407. final item = carrito[index];
  408. return Column(
  409. children: [
  410. ListTile(
  411. title: Text(item.producto.nombre!,
  412. style: const TextStyle(fontWeight: FontWeight.w600)),
  413. subtitle: Text('\$${item.producto.precio}',
  414. style: const TextStyle(
  415. fontWeight: FontWeight.bold,
  416. color: Color(0xFF008000))),
  417. trailing: Row(
  418. mainAxisSize: MainAxisSize.min,
  419. children: [
  420. IconButton(
  421. icon: const Icon(Icons.delete, color: Colors.red),
  422. onPressed: () =>
  423. eliminarProductoDelCarrito(index)),
  424. IconButton(
  425. icon: const Icon(Icons.remove),
  426. onPressed: () => quitarProductoDelCarrito(item)),
  427. const SizedBox(width: 5),
  428. Text('${item.cantidad}',
  429. style: const TextStyle(
  430. fontWeight: FontWeight.bold, fontSize: 14)),
  431. const SizedBox(width: 5),
  432. IconButton(
  433. icon: const Icon(Icons.add),
  434. onPressed: () => incrementarProducto(item)),
  435. ],
  436. ),
  437. ),
  438. if (item.selectableToppings.isNotEmpty)
  439. ExpansionTile(
  440. title: Text('Toppings',
  441. style: const TextStyle(
  442. fontWeight: FontWeight.bold, fontSize: 16)),
  443. children: item.selectableToppings.entries.map((entry) {
  444. final categoryId = entry.key;
  445. final availableToppings = entry.value;
  446. final categoria =
  447. categorias.firstWhere((c) => c.id == categoryId);
  448. return Column(
  449. crossAxisAlignment: CrossAxisAlignment.start,
  450. children: [
  451. Padding(
  452. padding: const EdgeInsets.only(left: 16.0),
  453. child: Text(
  454. categoria.descripcion!,
  455. style: const TextStyle(
  456. fontWeight: FontWeight.bold,
  457. fontSize: 16),
  458. ),
  459. ),
  460. ...availableToppings.map((topping) {
  461. ValueNotifier<bool> isSelectedNotifier =
  462. ValueNotifier<bool>(
  463. item.selectedToppings[categoryId]
  464. ?.contains(topping.id) ??
  465. false,
  466. );
  467. return ValueListenableBuilder<bool>(
  468. valueListenable: isSelectedNotifier,
  469. builder: (context, isSelected, _) {
  470. return CheckboxListTile(
  471. activeColor: AppTheme.primary,
  472. title: Row(
  473. mainAxisAlignment:
  474. MainAxisAlignment.spaceBetween,
  475. children: [
  476. Text(topping.nombre!),
  477. if (double.tryParse(
  478. topping.precio!) !=
  479. 0.0)
  480. Text(
  481. '+\$${topping.precio}',
  482. style: TextStyle(
  483. color: Colors.black,
  484. fontSize: 13,
  485. ),
  486. ),
  487. ],
  488. ),
  489. value: isSelected,
  490. onChanged: (bool? value) {
  491. final maximoToppings =
  492. categoria.maximo ?? 0;
  493. if (value == true) {
  494. if ((item.selectedToppings[categoryId]
  495. ?.length ??
  496. 0) >=
  497. maximoToppings) {
  498. item.selectedToppings[categoryId]!
  499. .remove(item
  500. .selectedToppings[
  501. categoryId]!
  502. .first);
  503. }
  504. item.selectedToppings[categoryId] ??=
  505. <int>{};
  506. item.selectedToppings[categoryId]!
  507. .add(topping.id!);
  508. } else {
  509. item.selectedToppings[categoryId]
  510. ?.remove(topping.id!);
  511. if (item.selectedToppings[categoryId]
  512. ?.isEmpty ??
  513. false) {
  514. item.selectedToppings
  515. .remove(categoryId);
  516. }
  517. }
  518. setState(() {});
  519. },
  520. );
  521. },
  522. );
  523. }).toList(),
  524. ],
  525. );
  526. }).toList(),
  527. ),
  528. Divider(),
  529. ],
  530. );
  531. },
  532. ),
  533. ),
  534. const Divider(thickness: 5),
  535. _buildTotalSection(),
  536. const SizedBox(height: 25),
  537. Padding(
  538. padding: const EdgeInsets.all(8.0),
  539. child: ElevatedButton(
  540. onPressed: _finalizeOrder,
  541. style: ElevatedButton.styleFrom(
  542. primary: AppTheme.tertiary,
  543. textStyle: const TextStyle(fontSize: 22),
  544. fixedSize: const Size(250, 50),
  545. ),
  546. child: Text('Finalizar Pedido',
  547. style: TextStyle(color: AppTheme.quaternary)),
  548. ),
  549. ),
  550. ],
  551. ),
  552. );
  553. }
  554. void eliminarProductoDelCarrito(int index) {
  555. setState(() {
  556. carrito.removeAt(index);
  557. });
  558. }
  559. void incrementarProducto(ItemCarrito item) {
  560. setState(() {
  561. item.cantidad++;
  562. });
  563. }
  564. void quitarProductoDelCarrito(ItemCarrito item) {
  565. setState(() {
  566. if (item.cantidad > 1) {
  567. item.cantidad--;
  568. } else {
  569. carrito.remove(item);
  570. }
  571. });
  572. }
  573. Widget _buildProductsSection() {
  574. return Column(
  575. children: [
  576. // _buildSearchBar(),
  577. const SizedBox(height: 10),
  578. _buildCategoryButtons(),
  579. const SizedBox(height: 15),
  580. Expanded(
  581. child: Consumer<ProductoViewModel>(builder: (context, model, child) {
  582. productos = model.productos;
  583. return GridView.builder(
  584. controller: _gridViewController,
  585. key: ValueKey<int>(categoriaSeleccionada?.id ?? 0),
  586. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  587. crossAxisCount: 3,
  588. childAspectRatio: 3 / 2,
  589. ),
  590. itemCount: productos.length,
  591. itemBuilder: (context, index) {
  592. final producto = productos[index];
  593. if (producto.idCategoria != categoriaSeleccionada?.id) {
  594. return Container();
  595. }
  596. return Card(
  597. child: InkWell(
  598. onTap: () => agregarAlCarrito(producto),
  599. child: Column(
  600. mainAxisAlignment: MainAxisAlignment.center,
  601. children: [
  602. if (producto.imagen != null &&
  603. File(producto.imagen!).existsSync())
  604. Image.file(
  605. File(producto.imagen!),
  606. height: 120,
  607. fit: BoxFit.cover,
  608. )
  609. else
  610. const Icon(Icons.fastfood, size: 80),
  611. const SizedBox(height: 8),
  612. Padding(
  613. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  614. child: Text(
  615. producto.nombre ?? '',
  616. style: const TextStyle(
  617. fontSize: 16,
  618. fontWeight: FontWeight.bold,
  619. ),
  620. textAlign: TextAlign.center,
  621. ),
  622. ),
  623. const SizedBox(height: 8),
  624. Text(
  625. '\$${producto.precio}',
  626. style: const TextStyle(
  627. fontSize: 16,
  628. fontWeight: FontWeight.bold,
  629. color: Color(0xFF008000),
  630. ),
  631. ),
  632. ],
  633. ),
  634. ),
  635. );
  636. },
  637. );
  638. }),
  639. )
  640. ],
  641. );
  642. }
  643. Widget _buildCategoryButtons() {
  644. List<CategoriaProducto> categoriasFiltradas =
  645. categorias.where((categoria) => categoria.esToping == 0).toList();
  646. return Container(
  647. height: 50,
  648. child: ListView.builder(
  649. scrollDirection: Axis.horizontal,
  650. itemCount: categoriasFiltradas.length,
  651. itemBuilder: (context, index) {
  652. final categoria = categoriasFiltradas[index];
  653. bool isSelected = categoriaSeleccionada?.id == categoria.id;
  654. return Padding(
  655. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  656. child: ElevatedButton(
  657. onPressed: () {
  658. cargarProductosPorCategoria(categoria.id);
  659. setState(() {
  660. categoriaSeleccionada = categoria;
  661. });
  662. },
  663. style: ElevatedButton.styleFrom(
  664. primary: isSelected ? AppTheme.tertiary : Colors.grey,
  665. foregroundColor:
  666. isSelected ? AppTheme.quaternary : AppTheme.secondary,
  667. onPrimary: AppTheme.secondary,
  668. ),
  669. child: Text(categoria.nombre!),
  670. ),
  671. );
  672. },
  673. ),
  674. );
  675. }
  676. Widget _buildSearchBar() {
  677. return Padding(
  678. padding: const EdgeInsets.all(8.0),
  679. child: TextField(
  680. controller: _searchController,
  681. decoration: InputDecoration(
  682. hintText: 'Buscar producto...',
  683. prefixIcon: const Icon(Icons.search),
  684. border: OutlineInputBorder(
  685. borderRadius: BorderRadius.circular(12.0),
  686. ),
  687. ),
  688. onChanged: _onSearchChanged,
  689. ),
  690. );
  691. }
  692. }