pedido_mesa_screen.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. import 'package:conalep_pos/models/mesa_model.dart';
  2. import 'package:conalep_pos/models/models.dart';
  3. import 'package:conalep_pos/themes/themes.dart';
  4. import 'package:conalep_pos/viewmodels/mesa_view_model.dart';
  5. import 'package:conalep_pos/viewmodels/viewmodels.dart';
  6. import 'package:conalep_pos/views/pedido_mesa/pedido_mesa_detalle.dart';
  7. import 'package:conalep_pos/views/pedido_mesa/pedido_mesa_form.dart';
  8. import 'package:conalep_pos/widgets/widgets.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:provider/provider.dart';
  11. import '../../widgets/widgets_components.dart' as clase;
  12. class PedidoMesaScreen extends StatefulWidget {
  13. const PedidoMesaScreen({Key? key}) : super(key: key);
  14. @override
  15. State<PedidoMesaScreen> createState() => _PedidoMesaScreenState();
  16. }
  17. class _PedidoMesaScreenState extends State<PedidoMesaScreen> {
  18. final _busqueda = TextEditingController(text: '');
  19. DateTime? fechaInicio;
  20. DateTime? fechaFin;
  21. List<DropdownMenuItem<int>> listaMesas = [];
  22. int? selectedMesa = 0;
  23. ScrollController horizontalScrollController = ScrollController();
  24. @override
  25. void initState() {
  26. super.initState();
  27. WidgetsBinding.instance.addPostFrameCallback((_) {
  28. Provider.of<PedidoViewModel>(context, listen: false)
  29. .fetchLocalMesaPedidosForScreen();
  30. final mvm = Provider.of<MesaViewModel>(context, listen: false);
  31. // Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
  32. final mesas = mvm.mesas;
  33. if (mesas.isNotEmpty) {
  34. mesas.sort((a, b) => a.nombre!.compareTo(b.nombre!));
  35. listaMesas = mesas
  36. .map(
  37. (mesa) => DropdownMenuItem<int>(
  38. value: mesa.id,
  39. child: Text(
  40. '${mesa.clave} - ${mesa.nombre}',
  41. style: const TextStyle(color: Colors.black),
  42. ),
  43. ),
  44. )
  45. .toList();
  46. }
  47. listaMesas.add(DropdownMenuItem<int>(
  48. value: 0,
  49. child: Text(
  50. 'Seleccionar Mesa',
  51. style: const TextStyle(color: Colors.black),
  52. ),
  53. ));
  54. });
  55. }
  56. // void exportCSV() async {
  57. // final pedidosViewModel =
  58. // Provider.of<PedidoViewModel>(context, listen: false);
  59. // List<Pedido> pedidosConProductos = [];
  60. // for (Pedido pedido in pedidosViewModel.pedidos) {
  61. // Pedido? pedidoConProductos =
  62. // await pedidosViewModel.fetchPedidoConProductos(pedido.id);
  63. // if (pedidoConProductos != null) {
  64. // pedidosConProductos.add(pedidoConProductos);
  65. // }
  66. // }
  67. // if (pedidosConProductos.isNotEmpty) {
  68. // String fileName = 'Pedidos_OlivaMia_POS';
  69. // if (fechaInicio != null && fechaFin != null) {
  70. // String startDateStr = DateFormat('dd-MM-yyyy').format(fechaInicio!);
  71. // String endDateStr = DateFormat('dd-MM-yyyy').format(fechaFin!);
  72. // fileName += '_${startDateStr}_al_${endDateStr}';
  73. // }
  74. // fileName += '.csv';
  75. // await exportarPedidosACSV(pedidosConProductos, fileName);
  76. // ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  77. // content: Text('Archivo CSV descargado! Archivo: $fileName')));
  78. // } else {
  79. // ScaffoldMessenger.of(context).showSnackBar(
  80. // SnackBar(content: Text('No hay pedidos para exportar.')));
  81. // }
  82. // }
  83. void clearSearchAndReset() {
  84. setState(() {
  85. _busqueda.clear();
  86. fechaInicio = null;
  87. fechaFin = null;
  88. selectedMesa = 0;
  89. Provider.of<PedidoViewModel>(context, listen: false)
  90. .fetchLocalMesaPedidosForScreen();
  91. });
  92. }
  93. void go(Pedido item) async {
  94. Pedido? pedidoCompleto =
  95. await Provider.of<PedidoViewModel>(context, listen: false)
  96. .fetchPedidoConProductos(item.id);
  97. if (pedidoCompleto != null) {
  98. if (pedidoCompleto.estatus == 'EN PROCESO') {
  99. Navigator.push(
  100. context,
  101. MaterialPageRoute(
  102. builder: (context) => PedidoMesaForm(pedido: pedidoCompleto),
  103. ),
  104. );
  105. } else {
  106. Navigator.push(
  107. context,
  108. MaterialPageRoute(
  109. builder: (context) =>
  110. PedidoMesaDetalleScreen(pedido: pedidoCompleto),
  111. ),
  112. );
  113. }
  114. } else {
  115. print("Error al cargar el pedido con productos");
  116. }
  117. }
  118. @override
  119. Widget build(BuildContext context) {
  120. final pvm = Provider.of<PedidoViewModel>(context);
  121. double screenWidth = MediaQuery.of(context).size.width;
  122. final isMobile = screenWidth < 1250;
  123. final double? columnSpacing = isMobile ? null : 0;
  124. TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
  125. List<DataRow> registros = [];
  126. for (Pedido item in pvm.pedidos) {
  127. registros.add(DataRow(cells: [
  128. DataCell(
  129. Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
  130. PopupMenuButton(
  131. itemBuilder: (context) => [
  132. PopupMenuItem(
  133. child: const Text('Detalle'),
  134. onTap: () => go(item),
  135. ),
  136. PopupMenuItem(
  137. child: const Text('Cancelar Pedido'),
  138. onTap: () async {
  139. bool confirmado = await showDialog<bool>(
  140. context: context,
  141. builder: (context) {
  142. return AlertDialog(
  143. title: const Text("Cancelar Pedido",
  144. style: TextStyle(
  145. fontWeight: FontWeight.w500, fontSize: 22)),
  146. content: const Text(
  147. '¿Estás seguro de que deseas cancelar este pedido?',
  148. style: TextStyle(fontSize: 18)),
  149. actions: [
  150. Row(
  151. mainAxisAlignment:
  152. MainAxisAlignment.spaceBetween,
  153. children: [
  154. TextButton(
  155. onPressed: () =>
  156. Navigator.of(context).pop(false),
  157. child: const Text('No',
  158. style: TextStyle(fontSize: 18)),
  159. style: ButtonStyle(
  160. padding: MaterialStatePropertyAll(
  161. EdgeInsets.fromLTRB(
  162. 20, 10, 20, 10)),
  163. backgroundColor:
  164. MaterialStatePropertyAll(
  165. Colors.red),
  166. foregroundColor:
  167. MaterialStatePropertyAll(
  168. AppTheme.secondary)),
  169. ),
  170. TextButton(
  171. onPressed: () =>
  172. Navigator.of(context).pop(true),
  173. child: const Text('Sí',
  174. style: TextStyle(fontSize: 18)),
  175. style: ButtonStyle(
  176. padding: MaterialStatePropertyAll(
  177. EdgeInsets.fromLTRB(
  178. 20, 10, 20, 10)),
  179. backgroundColor:
  180. MaterialStatePropertyAll(
  181. AppTheme.tertiary),
  182. foregroundColor:
  183. MaterialStatePropertyAll(
  184. AppTheme.quaternary)),
  185. ),
  186. ],
  187. )
  188. ],
  189. );
  190. },
  191. ) ??
  192. false;
  193. if (confirmado) {
  194. await Provider.of<PedidoViewModel>(context, listen: false)
  195. .cancelarPedido(item.id);
  196. ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  197. content: Text("Pedido cancelado correctamente")));
  198. }
  199. },
  200. )
  201. ],
  202. icon: const Icon(Icons.more_vert),
  203. )
  204. ])),
  205. DataCell(
  206. Text(item.peticion ?? "Sin fecha"),
  207. onTap: () => go(item),
  208. ),
  209. DataCell(
  210. Text(Provider.of<MesaViewModel>(context, listen: false)
  211. .fetchLocalById(idMesa: item.idMesa)
  212. .nombre
  213. .toString()),
  214. onTap: () => go(item),
  215. ),
  216. /* DataCell(
  217. Text(item.id.toString()),
  218. onTap: () => go(item),
  219. ), */
  220. DataCell(
  221. Text(item.folio.toString()),
  222. onTap: () => go(item),
  223. ),
  224. /* DataCell(
  225. Text(item.idLocal.toString()),
  226. onTap: () => go(item),
  227. ), */
  228. DataCell(
  229. Text(item.nombreCliente ?? "Sin nombre"),
  230. onTap: () => go(item),
  231. ),
  232. DataCell(
  233. Text(item.comentarios ?? "Sin comentarios"),
  234. onTap: () => go(item),
  235. ),
  236. DataCell(
  237. Text(item.estatus ?? "Sin Estatus"),
  238. onTap: () => go(item),
  239. ),
  240. ]));
  241. }
  242. return Scaffold(
  243. appBar: AppBar(
  244. title: Text(
  245. 'Pedidos de Mesa',
  246. style: TextStyle(
  247. color: AppTheme.secondary, fontWeight: FontWeight.w500),
  248. ),
  249. // actions: <Widget>[
  250. // IconButton(
  251. // icon: const Icon(Icons.save_alt),
  252. // onPressed: exportCSV,
  253. // tooltip: 'Exportar a CSV',
  254. // ),
  255. // ],
  256. iconTheme: IconThemeData(color: AppTheme.secondary)),
  257. floatingActionButton: FloatingActionButton.extended(
  258. onPressed: () async {
  259. await Navigator.push(
  260. context,
  261. MaterialPageRoute(
  262. builder: (context) => PedidoMesaForm(),
  263. ),
  264. ).then((_) => Provider.of<PedidoViewModel>(context, listen: false)
  265. .fetchLocalPedidosForScreen());
  266. },
  267. icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
  268. label: Text(
  269. "Agregar Pedido",
  270. style: TextStyle(fontSize: 20, color: AppTheme.quaternary),
  271. ),
  272. shape: RoundedRectangleBorder(
  273. borderRadius: BorderRadius.circular(8),
  274. ),
  275. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  276. backgroundColor: AppTheme.tertiary,
  277. ),
  278. body: Column(
  279. children: [
  280. Expanded(
  281. child: ListView(
  282. padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
  283. children: [
  284. const SizedBox(height: 8),
  285. clase.tarjeta(
  286. Padding(
  287. padding: const EdgeInsets.all(8.0),
  288. child: LayoutBuilder(
  289. builder: (context, constraints) {
  290. if (screenWidth > 1000) {
  291. return Row(
  292. crossAxisAlignment: CrossAxisAlignment.end,
  293. children: [
  294. Expanded(
  295. flex: 7,
  296. child: _buildDateRangePicker(),
  297. ),
  298. const SizedBox(width: 5),
  299. botonBuscar()
  300. ],
  301. );
  302. } else {
  303. return Column(
  304. children: [
  305. Row(
  306. children: [_buildDateRangePicker()],
  307. ),
  308. Row(
  309. children: [botonBuscar()],
  310. ),
  311. ],
  312. );
  313. }
  314. },
  315. ),
  316. ),
  317. ),
  318. const SizedBox(height: 8),
  319. pvm.isLoading
  320. ? const Center(child: CircularProgressIndicator())
  321. : Container(),
  322. clase.tarjeta(
  323. Column(
  324. children: [
  325. LayoutBuilder(builder: (context, constraints) {
  326. return SingleChildScrollView(
  327. scrollDirection: Axis.vertical,
  328. child: Scrollbar(
  329. controller: horizontalScrollController,
  330. interactive: true,
  331. thumbVisibility: true,
  332. thickness: 10.0,
  333. child: SingleChildScrollView(
  334. controller: horizontalScrollController,
  335. scrollDirection: Axis.horizontal,
  336. child: ConstrainedBox(
  337. constraints: BoxConstraints(
  338. minWidth: isMobile
  339. ? constraints.maxWidth
  340. : screenWidth),
  341. child: DataTable(
  342. columnSpacing: columnSpacing,
  343. sortAscending: true,
  344. sortColumnIndex: 1,
  345. columns: [
  346. DataColumn(label: Text(" ", style: estilo)),
  347. DataColumn(
  348. label: Text("FECHA", style: estilo)),
  349. DataColumn(
  350. label: Text("MESA", style: estilo)),
  351. /* DataColumn(
  352. label: Text("ID", style: estilo)), */
  353. DataColumn(
  354. label: Text("FOLIO", style: estilo)),
  355. /* DataColumn(
  356. label: Text("IDLOCAL", style: estilo)), */
  357. DataColumn(
  358. label: Text("NOMBRE", style: estilo)),
  359. DataColumn(
  360. label:
  361. Text("COMENTARIOS", style: estilo)),
  362. DataColumn(
  363. label: Text("ESTATUS", style: estilo)),
  364. ],
  365. rows: registros,
  366. ),
  367. ),
  368. ),
  369. ),
  370. );
  371. }),
  372. ],
  373. ),
  374. ),
  375. const SizedBox(height: 15),
  376. if (!pvm.isLoading)
  377. Row(
  378. mainAxisAlignment: MainAxisAlignment.center,
  379. children: [
  380. TextButton(
  381. onPressed:
  382. pvm.currentPage > 1 ? pvm.previousPage : null,
  383. child: Text('Anterior'),
  384. style: ButtonStyle(
  385. backgroundColor:
  386. MaterialStateProperty.resolveWith<Color?>(
  387. (Set<MaterialState> states) {
  388. if (states.contains(MaterialState.disabled)) {
  389. return Colors.grey;
  390. }
  391. return AppTheme.tertiary;
  392. },
  393. ),
  394. foregroundColor:
  395. MaterialStateProperty.resolveWith<Color?>(
  396. (Set<MaterialState> states) {
  397. if (states.contains(MaterialState.disabled)) {
  398. return Colors.black;
  399. }
  400. return Colors.white;
  401. },
  402. ),
  403. ),
  404. ),
  405. SizedBox(width: 15),
  406. Text('Página ${pvm.currentPage} de ${pvm.totalPages}'),
  407. SizedBox(width: 15),
  408. TextButton(
  409. onPressed: pvm.currentPage < pvm.totalPages
  410. ? pvm.nextPage
  411. : null,
  412. child: Text('Siguiente'),
  413. style: ButtonStyle(
  414. backgroundColor:
  415. MaterialStateProperty.resolveWith<Color?>(
  416. (Set<MaterialState> states) {
  417. if (states.contains(MaterialState.disabled)) {
  418. return Colors.grey;
  419. }
  420. return AppTheme.tertiary;
  421. },
  422. ),
  423. foregroundColor:
  424. MaterialStateProperty.resolveWith<Color?>(
  425. (Set<MaterialState> states) {
  426. if (states.contains(MaterialState.disabled)) {
  427. return Colors.black;
  428. }
  429. return Colors.white;
  430. },
  431. ),
  432. ),
  433. ),
  434. ],
  435. ),
  436. const SizedBox(height: 15),
  437. ],
  438. ),
  439. ),
  440. ],
  441. ),
  442. );
  443. }
  444. Widget _buildDateRangePicker() {
  445. return Row(
  446. children: [
  447. Expanded(
  448. flex: 4,
  449. child: AppTextField(
  450. prefixIcon: const Icon(Icons.search),
  451. etiqueta: 'Búsqueda por folio...',
  452. controller: _busqueda,
  453. hintText: 'Búsqueda por folio...',
  454. ),
  455. ),
  456. const SizedBox(width: 5),
  457. Expanded(
  458. flex: 4,
  459. child: clase.FechaSelectWidget(
  460. fecha: fechaInicio,
  461. onFechaChanged: (d) {
  462. setState(() {
  463. fechaInicio = d;
  464. });
  465. },
  466. etiqueta: "Fecha Inicial",
  467. context: context,
  468. ),
  469. ),
  470. const SizedBox(width: 5),
  471. Expanded(
  472. flex: 4,
  473. child: clase.FechaSelectWidget(
  474. fecha: fechaFin,
  475. onFechaChanged: (d) {
  476. setState(() {
  477. fechaFin = d;
  478. });
  479. },
  480. etiqueta: "Fecha Final",
  481. context: context,
  482. ),
  483. ),
  484. const SizedBox(width: 5),
  485. Expanded(
  486. flex: 4,
  487. child: AppDropdownModel<int>(
  488. etiqueta: "Mesa",
  489. hint: 'Seleccionar',
  490. items: listaMesas,
  491. selectedValue: selectedMesa,
  492. onChanged: (value) {
  493. setState(() {
  494. selectedMesa = value;
  495. });
  496. },
  497. ),
  498. )
  499. ],
  500. );
  501. }
  502. Widget botonBuscar() {
  503. return Expanded(
  504. flex: 2,
  505. child: Row(
  506. children: [
  507. Expanded(
  508. flex: 2,
  509. child: Padding(
  510. padding: const EdgeInsets.only(bottom: 5),
  511. child: ElevatedButton(
  512. onPressed: clearSearchAndReset,
  513. style: ElevatedButton.styleFrom(
  514. shape: RoundedRectangleBorder(
  515. borderRadius: BorderRadius.circular(20.0),
  516. ),
  517. backgroundColor: AppTheme.tertiary,
  518. padding: const EdgeInsets.symmetric(vertical: 25),
  519. ),
  520. child: Text('Limpiar',
  521. style: TextStyle(color: AppTheme.quaternary)),
  522. ),
  523. ),
  524. ),
  525. const SizedBox(width: 8),
  526. Expanded(
  527. flex: 2,
  528. child: Padding(
  529. padding: const EdgeInsets.only(bottom: 5),
  530. child: ElevatedButton(
  531. onPressed: () async {
  532. if (_busqueda.text.isNotEmpty) {
  533. await Provider.of<PedidoViewModel>(context, listen: false)
  534. .buscarPedidosPorFolio(_busqueda.text.trim());
  535. } else if (fechaInicio != null && fechaFin != null) {
  536. await Provider.of<PedidoViewModel>(context, listen: false)
  537. .buscarPedidosPorFecha(fechaInicio!, fechaFin!);
  538. } else if (selectedMesa != null && selectedMesa != 0) {
  539. await Provider.of<PedidoViewModel>(context, listen: false)
  540. .buscarPedidosPorMesa(selectedMesa!);
  541. } else {
  542. ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
  543. content: Text(
  544. 'Introduce un folio o selecciona un rango de fechas para buscar.')));
  545. }
  546. },
  547. style: ElevatedButton.styleFrom(
  548. shape: RoundedRectangleBorder(
  549. borderRadius: BorderRadius.circular(20.0),
  550. ),
  551. backgroundColor: AppTheme.tertiary,
  552. padding: const EdgeInsets.symmetric(vertical: 25),
  553. ),
  554. child: Text('Buscar',
  555. style: TextStyle(color: AppTheme.quaternary)),
  556. ),
  557. ),
  558. ),
  559. ],
  560. ));
  561. }
  562. }